One contract hash family: git-blob-sha1 everywhere, and reconcile reads the registry
rank: decision outranks: []
Section titled “rank: decision outranks: []”One contract hash family: git-blob-sha1 everywhere, and reconcile reads the registry
Section titled “One contract hash family: git-blob-sha1 everywhere, and reconcile reads the registry”Date: 2026-10-01 Status: closed Supersedes: none Superseded-by: none — current MR-compliance: MR-7, MR-14
Context
Section titled “Context”docs/decisions/2026-05-24-contract-sha-hash-method-fix-hermes.md (closed)
found two hash families for the same file and ruled the git-blob-sha1 one
canonical, matching registry.yaml and git hash-object. It named a follow-up,
“petrova doctor should emit git-blob-sha1, not sha256”, and deferred it to
“its own PR + test coverage + decision doc”. That follow-up was never done.
On main at 58f38241:
| writer | field | hash |
|---|---|---|
cli/src/verbs/sweep_state.ts:106-110 (gitBlobShortSha) | state.actual_contract_sha | git-blob-sha1 |
registry.yaml (petrova_act_register_consumer.ts:130, reconcile) | repos[].contract_sha | git-blob-sha1 |
cli/src/verbs/doctor.ts:73, written at :157 | state.contract_sha | sha256 |
docs/findings/20260825-1200-a-state-field-nothing-writes-outlives-the-value-it-copied.md
(PTV-FND-0059) then recorded state.contract_sha as a stale copy that no writer
maintained. Since #499 that has changed. The CI deep probe runs
doctor --commit-state every 8 hours, so the field is now rewritten on every
pass, in the wrong family. Measured on 2026-10-01 over the 26 state files that
carry it: 0 equal actual_contract_sha, 1 equals the registry value,
25 equal neither. The 2026-10-01 triage of kahn-hq and rocky-hq read
exactly this as drift that was not there.
The field also has a consumer that assumes a third meaning.
cli/src/verbs/petrova_act_reconcile_drift.ts asks the caller for a from_sha,
documented as what they “believe is currently registered” (:16-22). It then
guards it against state.contract_sha (:94) and reports that value as
before / “registered” in the PR body (:125, :159). Its own test fixture
sets state and registry to the same value (cli/tests/reconcile_drift.test.ts:34-56),
so it was written for a world where state.contract_sha mirrors the registry.
Against today’s state files the guard can only pass if the operator copies a
sha256 that appears nowhere else, and the PR it opens names that sha256 as the
registered value.
Decision
Section titled “Decision”One hash family, git-blob-sha1 truncated to 12 hex, for every contract sha. Each field has one meaning.
doctoremits git-blob-sha1. MovegitBlobShortShaout ofsweep_state.tsintocli/src/contract.tsand use it indoctor.ts. This is the 2026-05-24 follow-up, as ruled there.state.contract_shameans “the contract doctor last probed”. Written by doctor only. After (1) it equalsactual_contract_shawhenever the deep probe and the sweep saw the same commit.contracts/state.schema.jsongains a description saying so, and that it is not the registered value.reconcile_driftreads the registry, not the state file, for both itsfrom_shaguard and its reportedbefore. That is the value its docstring already says the caller is asserting.FROM_SHA_MISMATCHthen means what its message says.
No data migration is needed. The next deep-probe pass rewrites every
state.contract_sha in the new family, and the registry is already
git-blob-sha1.
Alternatives considered
Section titled “Alternatives considered”- Drop
state.contract_shafrom the state schema — rejected for now. The host readscontract_shaonRepoState(host/src/sources/petrova.ts:46), and removal is a consumer-visible schema change for the eva/petrova consumption contracts. With (1) and (2) the field is correct and maintained; removal can be its own decision if it still earns nothing. - Fix
doctoronly — rejected.state.contract_shawould then equalactual_contract_sha, soreconcile_driftwould reportbefore == afterand open a PR claiming to bump a sha to itself. - Make the sweep overwrite
state.contract_shawith the registry value — rejected. That gives one field two writers with two meanings, which is PTV-FND-0059’s failure shape again.
Consequences
Section titled “Consequences”For code:
cli/src/contract.ts: exportgitBlobShortSha.sweep_state.tsanddoctor.tsimport it.petrova_act_reconcile_drift.ts: guard andbeforefromregistry.yaml. Messages and the docstring are updated to match.- Tests: doctor’s
contract_shaequalsgit hash-objectfor a fixture. The reconcile guard passes on the registry sha and refuses a state-only sha.
For docs:
contracts/state.schema.jsongets acontract_shadescription.- PTV-FND-0059 can close by reference to this record once the first post-merge deep probe lands.
For in-flight phases: none.
For invariants: none added or repealed. MR-14 is unchanged: promotion
to registry.yaml stays a human-merged PR.
References
Section titled “References”docs/decisions/2026-05-24-contract-sha-hash-method-fix-hermes.mddocs/findings/20260825-1200-a-state-field-nothing-writes-outlives-the-value-it-copied.md(PTV-FND-0059)docs/findings/20260922-1330-null0-toll-contract-incomplete-stub.mddocs/decisions/2026-10-01-deep-probe-runs-in-ci.md, #499
Sign-off
Section titled “Sign-off”- Subagent: Claude Opus 5.5, 2026-10-01 (drafted; evidence above read at
58f38241) - Human: flight@arno.host — that every contract sha is git-blob-sha1, that
state.contract_shameans “last probed by doctor”, and thatreconcile_driftguards against the registry.
Countersigned by human:flight@arno.host on 2026-10-01 — box ticked in
commit 8bda31dd, authored by the human (pushed to the #501 head branch after
merge; cherry-picked here). Status flipped open→closed on 2026-10-01 by an
agent as scribe (MR-7).