# OSS-ADR-0051 — The grade-root derivation is a versioned protocol shape (`certifiedRoot`) — extends OSS-ADR-0046

Status: accepted (2026-07-17, owner — arch-review, arch-deepening-2026-07-15)

## Context

Today the signed grade `root = sha256(canonicalize(signingPayload(fields)))` is
derived **only in the platform** (`apps/api/src/grade/signer.ts`:
`SIGNATURE_COVERED_GRADE_FIELDS` + `signingPayload`). OSS `grade()` reproduces the
NUMBERS and `canonicalize` (dependency-free protocol, OSS-ADR-0046) reproduces the
BYTES, but **no outsider-runnable step rebuilds the signed ROOT** — so recompute
stops at the numbers, not the root the trust substrate rests on.

OSS `verify.ts` deliberately checks only signature-over-root, documenting that
reproducing the root "would import control-plane grade schema into OSS." That
boundary is exactly the gap: the covered-field key set and its order — the SHAPE
the signature commits to — is private, so an outsider can recompute every number a
grade reports and still not reconstruct the thing that was signed. This ADR removes
that gap by making the schema PUBLIC dependency-free protocol, so the previously
control-plane-only derivation can be imported by the light verifier without dragging
control-plane schema along with it.

This extends OSS-ADR-0046 ("the protocol package owns the wire shapes and stays
dependency-free"): the grade-root *derivation* is one more wire shape that belongs
in `src/protocol`.

## Decision

1. **The root derivation is owned by the OSS `protocol` package as a pure function
   `certifiedRoot(coveredFields)`.** The canonicalize+sha256 procedure and the named
   covered-field key set + their order live in protocol as a pure function — no key,
   no wall clock. Platform signer and OSS CLI both import it. The private signing key
   stays platform-side; the open/closed minting seam (OSS-ADR-0001/0009) is preserved
   — only the *derivation* is shared, never the key. Platform stops owning the root
   SHAPE (it still owns the VALUES it feeds in and the key it signs with).

2. **The covered-field key set is versioned with `PROTOCOL_VERSION` and is
   additive-optional.** An absent field reads UNKNOWN (Certified-evidence doctrine,
   CONTEXT.md), so additive fields do not break prior-version recompute; a breaking
   change to the set or its order is a coordinated protocol bump that **fails closed
   on mismatch**, never a silent divergence.

3. **Recompute closes in TWO outsider-runnable layers.**
   - `verify` (light, node-only, imports only the dependency-free protocol
     `canonicalize`) checks `root == hash(canonicalize(published covered-fields))` —
     binding the signature to the published numbers.
   - `certify` (heavy, already reprojects the Blotter through the engine)
     additionally checks `published numbers == grade(reproject(tape))` — binding the
     numbers to the tape.
   Together they close the previously-deferred "L3" leg: root ↔ payload ↔ tape is now
   recomputable end-to-end by any outsider.

4. **`issuedAt`, `kid`, `epoch` are INPUTS to `certifiedRoot`, never generated in
   OSS** — preserving determinism (no wall clock on the OSS path). `issuedAt` stays
   signature-covered (attested, non-backdatable).

## Consequences

- `verify.ts`'s prior boundary comment ("does NOT reproduce the platform's canonical
  grade-root derivation") is **superseded**: the schema is now public dependency-free
  protocol, so importing it into the light verifier does not drag control-plane schema
  in. The comment must be updated when the change lands.
- The trust substrate strengthens: an outsider can now deterministically rebuild the
  signed root, not merely the numbers under it — recomputability, the property
  KestrelBench rests on, extends all the way to the signature.
- Platform's grade signer shrinks to values-in / key-sign; the SHAPE it used to own
  moves to protocol, and root bytes must stay **byte-identical to today** (every
  previously-signed grade still verifies).
- **Prerequisite:** the shared attestation constants (bead `kestrel-s67u`) must land
  first so `src/protocol/attestation.ts` exists as the home for `certifiedRoot` and
  its covered-field key set.

## References

- OSS-ADR-0046 — the protocol package owns the wire shapes and stays dependency-free
  (this ADR extends it: the root derivation is one more owned shape).
- OSS-ADR-0001 / OSS-ADR-0009 — the open/closed minting seam preserved (key stays
  platform-side; only the derivation is shared).
- `kestrel-s67u` — shared attestation constants; prerequisite (home for the derivation).
- `apps/api/src/grade/signer.ts` (platform, `SIGNATURE_COVERED_GRADE_FIELDS` /
  `signingPayload`), `src/cli/commands/verify.ts` (light verifier + boundary comment),
  `src/protocol` (`canonicalize`, `PROTOCOL_VERSION`, `attestation.ts`).
