Skip to content

ledger: /verify reports ok:false — content_mismatch at seq 5 on a 3-day-old chain #9850

Description

@JSONbored

Observed on a live self-host instance

GET /v1/public/decision-ledger/verify
{"ok":false,"checked":4,"tipSeq":1639,"totalCount":1639,"prunedRecords":0,
 "break":{"kind":"content_mismatch","atSeq":5,
          "recordId":"record:JSONbored/loopover#8921@a2446db8e808529d19803ada464cc9d605a89638"}}

Verification stops at the first break, so 4 of 1,639 rows are checked and the endpoint publicly reports
ok: false.
Anyone running the documented verification against this deployment sees a chain break.

Why this is probably not tampering

The chain is only three days old — seq 1 @ 2026-07-26T13:40:09Z, seq 1639 @ 2026-07-29T14:01:03Z — and
prunedRecords: 0, so the legitimate pruned-preimage explanation does not apply. The instance has been
tracking beta images across that window (currently orb-v3.6.0-beta.6).

The likely cause is that the decision-record preimage changed between the release that wrote seq 5 and the
release verifying it now. Recomputing the digest over the current shape yields a different value than the one
committed at write time, and the content check correctly reports a mismatch.

Why that matters more than one bad row

If the record preimage is not stable across versions, then every schema change silently invalidates the
content check for every row written before it
— and the published /verify endpoint starts reporting
ok: false on an untampered ledger. The chain-structure guarantees (sequence, prevHash, rowHash) still hold;
it is specifically the content re-check that breaks.

That inverts what the surface is for. A verifier cannot distinguish "someone edited a record" from "the
software changed shape", which is exactly the discrimination the ledger exists to provide.

What needs deciding

  • Is the record preimage intended to be version-stable? If yes, this is a bug in whatever changed it, and
    the fix is a versioned preimage (a schemaVersion in the digest input, with the verifier recomputing under
    the row's own version) rather than a re-hash of history.
  • If it is NOT intended to be stable, /verify needs to say so — the same way prunedRecords already
    carves out legitimately-unre-checkable rows — so a shape change does not read as tampering.

Reproduce

Any deployment whose ledger spans a release that altered the decision-record shape. Confirmed above on a
3-day-old chain.

Links

src/review/decision-record.ts, src/review/ledger-anchor.ts, the /v1/public/decision-ledger/verify
handler in src/api/routes.ts. Related: #9271, #9267.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions