Context
Sub-issue of #9267 (external decision-ledger anchoring). buildLedgerAnchorPayload (src/review/decision-record.ts:376) returns { seq, rowHash, at } and is unused. Per the research on #9267, this shape cannot survive being read by a stranger in two years — it names neither the ledger it's from nor a schema version.
Requirements
- Extend the payload to a self-describing, versioned shape:
{ v: 1, ledger: "loopover.decision_ledger", seq, rowHash, totalCount, at }.
- Canonicalize and sign: reuse the existing
canonicalJson (decision-record.ts) over the payload, sign with ECDSA P-256 via Workers' native crypto.subtle — no new dependency.
- Key management: generate a signing keypair, store the private key as a Worker secret, and publish the public key (SPKI, DER) at a stable public route with an explicit rotation history (a verifier two years from now must be able to find the key that was valid when a given anchor was made).
GET /v1/public/decision-ledger/anchor-key (or similar) serving the current + any historical public keys, each with its validity window.
Deliverables
- Versioned/signed payload builder replacing the current
buildLedgerAnchorPayload
- Key generation + storage procedure (documented, since this is operational, not just code)
- Public key route + tests, including a rotation scenario (old anchors still verify against the historical key)
Expected outcome
Every anchor is independently verifiable as genuinely from this ledger, signed by a key a verifier can locate even after key rotation.
References
Sub-issue of #9267. Depends on #9269 conceptually (same trust surface) but can be built in parallel. buildLedgerAnchorPayload: src/review/decision-record.ts:376. canonicalJson: same file, :51.
Context
Sub-issue of #9267 (external decision-ledger anchoring).
buildLedgerAnchorPayload(src/review/decision-record.ts:376) returns{ seq, rowHash, at }and is unused. Per the research on #9267, this shape cannot survive being read by a stranger in two years — it names neither the ledger it's from nor a schema version.Requirements
{ v: 1, ledger: "loopover.decision_ledger", seq, rowHash, totalCount, at }.canonicalJson(decision-record.ts) over the payload, sign with ECDSA P-256 via Workers' nativecrypto.subtle— no new dependency.GET /v1/public/decision-ledger/anchor-key(or similar) serving the current + any historical public keys, each with its validity window.Deliverables
buildLedgerAnchorPayloadExpected outcome
Every anchor is independently verifiable as genuinely from this ledger, signed by a key a verifier can locate even after key rotation.
References
Sub-issue of #9267. Depends on #9269 conceptually (same trust surface) but can be built in parallel.
buildLedgerAnchorPayload:src/review/decision-record.ts:376.canonicalJson: same file,:51.