Problem
verifyDecisionLedger proves only that the ledger is self-consistent with itself. It cannot detect
the two tampering modes that matter most, because it emits no value an outside party can pin.
1. It returns no hash. The response is { ok, checked, nextAfterSeq, break? }
(src/review/decision-record.ts:233-256). Every hash it computes is discarded — prevHash = row.rowHash
stays a local. An observer polling daily has nothing to persist today and re-compare tomorrow. The chain
begins at a compile-time constant (LEDGER_GENESIS_HASH = "0".repeat(64), :184) and every row hash is
derived from data inside the same database the operator controls. Whoever controls that database can
rewrite any row, re-chain from genesis, and verify returns ok: true forever.
2. Tail truncation passes clean. The loop terminates when results runs out and returns
{ ok: true, checked } with no expected-length check:
246: for (const row of results) { ...gap / predecessor / row-hash checks... }
255: return { ok: true, checked, nextAfterSeq: results.length === bounded ? ... : null };
DELETE FROM decision_ledger WHERE seq > 100; — dropping the newest rows, i.e. the most recent and most
disputable decisions — leaves a chain that verifies perfectly. The existing tests cover mid-chain delete,
predecessor rewrite, and row rewrite (test/unit/decision-record.test.ts:202/207/212) but never tip
deletion, which is why this survived.
Why this is the load-bearing gap
migrations/0180_decision_ledger.sql:9-12 is honest about exactly this — "a self-operated chain is
tamper-EVIDENT, not tamper-PROOF — the operator can still rewrite wholesale" — and says "(module header
repeats this)". It does not: neither decision-record.ts's module header (~1-13) nor its ledger section
header (~181-201) carries the caveat. The only surface anyone outside the repo reads is the route comment
(src/api/routes.ts ~1266), which states the unqualified claim: "any observer can confirm no decision
was deleted, reordered, or rewritten."
This is threat T3 in #8136's table — "the maintainer's infrastructure is untrusted outright" — which
that spike classified as covered by neither reproducibility nor attestation, and deferred to #8540 as
demand-triggered. The trigger has fired: an external network is now asking us to substantiate the
trust story, and the decision ledger is the artifact we would point them at.
Anchoring is also what makes attestation worth having. The #8534 epic attests that code ran over data
with a given checksum; it says nothing about whether the recorded decision history is complete. Without
an anchor, an attested runner still reads from a rewritable ledger.
Requirements
- Return
tipSeq and tipHash (and the total row count) from verifyDecisionLedger, so a third party
can checkpoint. Add an explicit expected-length check so a short tail is a break, not ok: true.
- Publish a periodic external anchor of
{seq, rowHash, at} to a medium the operator cannot silently
rewrite. Evaluate at minimum:
- Verification must be reproducible by a non-JS implementer: publish the row-hash preimage definition and
the canonicalJson algorithm as a written spec with test vectors. Today the algorithm is correct
(recursive key sort, array order preserved, throws rather than silently mis-digesting) but exists only
as TypeScript — nobody can reimplement it against a spec.
- Propagate 0180's honest-limit paragraph into the module header and the route comment, and correct
the route comment now, ahead of the anchor landing. An overclaim on a public surface should not wait
for the fix.
Test Coverage Requirements
99%+ patch coverage, branch-counted. Explicit regression test for tip deletion (the case the current
suite misses), plus both arms of the length check and the anchor-publication path.
Links & Resources
Boundaries
Ledger verification, anchoring, and the honesty fix. No change to the record schema or to what decisions
are recorded (that is a sibling issue), and no TEE/attestation work (#8534 owns that).
maintainer-only — public trust-architecture authority.
Problem
verifyDecisionLedgerproves only that the ledger is self-consistent with itself. It cannot detectthe two tampering modes that matter most, because it emits no value an outside party can pin.
1. It returns no hash. The response is
{ ok, checked, nextAfterSeq, break? }(
src/review/decision-record.ts:233-256). Every hash it computes is discarded —prevHash = row.rowHashstays a local. An observer polling daily has nothing to persist today and re-compare tomorrow. The chain
begins at a compile-time constant (
LEDGER_GENESIS_HASH = "0".repeat(64),:184) and every row hash isderived from data inside the same database the operator controls. Whoever controls that database can
rewrite any row, re-chain from genesis, and
verifyreturnsok: trueforever.2. Tail truncation passes clean. The loop terminates when
resultsruns out and returns{ ok: true, checked }with no expected-length check:DELETE FROM decision_ledger WHERE seq > 100;— dropping the newest rows, i.e. the most recent and mostdisputable decisions — leaves a chain that verifies perfectly. The existing tests cover mid-chain delete,
predecessor rewrite, and row rewrite (
test/unit/decision-record.test.ts:202/207/212) but never tipdeletion, which is why this survived.
Why this is the load-bearing gap
migrations/0180_decision_ledger.sql:9-12is honest about exactly this — "a self-operated chain istamper-EVIDENT, not tamper-PROOF — the operator can still rewrite wholesale" — and says "(module header
repeats this)". It does not: neither
decision-record.ts's module header (~1-13) nor its ledger sectionheader (~181-201) carries the caveat. The only surface anyone outside the repo reads is the route comment
(
src/api/routes.ts~1266), which states the unqualified claim: "any observer can confirm no decisionwas deleted, reordered, or rewritten."
This is threat T3 in #8136's table — "the maintainer's infrastructure is untrusted outright" — which
that spike classified as covered by neither reproducibility nor attestation, and deferred to #8540 as
demand-triggered. The trigger has fired: an external network is now asking us to substantiate the
trust story, and the decision ledger is the artifact we would point them at.
Anchoring is also what makes attestation worth having. The #8534 epic attests that code ran over data
with a given checksum; it says nothing about whether the recorded decision history is complete. Without
an anchor, an attested runner still reads from a rewritable ledger.
Requirements
tipSeqandtipHash(and the total row count) fromverifyDecisionLedger, so a third partycan checkpoint. Add an explicit expected-length check so a short tail is a
break, notok: true.{seq, rowHash, at}to a medium the operator cannot silentlyrewrite. Evaluate at minimum:
network itself and the anchor lands in a ledger neither side controls.
Record the choice and its rationale on this issue in the Research: is TEE hardware attestation necessary, or does a reproducible-backtest checksum approach cover the real threat? #8136/Research: survey viable TEE/confidential-compute options on Cloudflare Workers/Containers #8137 two-spike format.
the
canonicalJsonalgorithm as a written spec with test vectors. Today the algorithm is correct(recursive key sort, array order preserved, throws rather than silently mis-digesting) but exists only
as TypeScript — nobody can reimplement it against a spec.
the route comment now, ahead of the anchor landing. An overclaim on a public surface should not wait
for the fix.
Test Coverage Requirements
99%+ patch coverage, branch-counted. Explicit regression test for tip deletion (the case the current
suite misses), plus both arms of the length check and the anchor-publication path.
Links & Resources
src/review/decision-record.ts~184-256;src/api/routes.ts~1266;migrations/0180_decision_ledger.sql~9-12execution-side sibling), Epic: attested evaluation on the hosted bare-metal execution plane #8534 (attested evaluation — complementary, not a substitute)
fix that first), orb(integrity): the decision-ledger verify endpoint never reconciles record_json against record_digest — the check its own comment promises does not exist #9078 (record↔digest reconciliation — the third missing check)
Boundaries
Ledger verification, anchoring, and the honesty fix. No change to the record schema or to what decisions
are recorded (that is a sibling issue), and no TEE/attestation work (#8534 owns that).
maintainer-only — public trust-architecture authority.