DashboardThree-Axis ProbesCandidate Registry Robustness › Iter 08

2026-06-16-candidate-registry-robustness-sota · iteration 08 · 2026-06-16

Iteration 08 — Tamper-evident, hash-chained history (P2/D6) DONE

Every change to a candidate is now a hash-chained event — editing any past entry out-of-band breaks the chain and the registry detects it. Backfilled onto all 44 rows and verified.

✅ What has been grounded

History is now tamper-evident. Each history event carries a fingerprint (entry_hash) computed from the previous event's fingerprint — like Git history or a mini-blockchain (Certificate-Transparency style). A row also stores its head_hash (the tip). Quietly editing a past note, stage, or date no longer matches the recomputed chain.
Tampering is provably detected. On a throwaway copy: changing a single past note made --verify-chain report history chain broken at seq 0 (exit 1). A normal stage change extends the chain cleanly and stays intact. --verify-chain is wired into --validate and /registry:doctor, and is its own /registry:verify-chain command.
Backfilled onto all 44 rows. --migrate now chains every row (44/44), idempotently. On the real ledger: validate PASS, tamper-evidence intact, all ids/stages/history preserved — purely additive (each event gains seq + prev_hash + entry_hash; each row gains head_hash).
Caught and fixed a real bug mid-iteration (honest visibility). The first cut silently did nothing because hashlib wasn't imported in the ledger tool — the throwaway test exposed it (0/44 chained). Fixed the import, re-ran: 44/44 chained, tampering detected. This is exactly why every change is tested on a copy before the real ledger is touched.

➡️ What I am doing next

1
Implement P2/D5 fully: pin each axis verdict to an input_lock (data snapshot + code SHA + params hash + harness version) + a subject_digest, so a verdict can't be reused on a different data window; tighten → promoting to also require a complete lock.
2
Implement D4: stable opaque spec-hash ids (id minted from the content hash; num demoted to a display ordinal).
3
Add stdlib structural validation (jsonschema is absent in the env) for unknown/typo'd fields.
4
Optional: enable the semantic Tier-3 (offline embedding) for abbreviation synonyms.

🎯 How this moves us toward the end goal

The audit trail is now trustworthy: nobody can quietly rewrite a candidate's history without it being detected. Together with the novelty gate, fleet-safe writes, lifecycle guards, and the promotion gate, the registry is now a robust, self-checking, tamper-evident system for stage-by-stage tracking — which is exactly the 'high-quality candidate tracking' the campaign set out to build. (Honest limit: the chain detects out-of-band edits; a fully adversarial actor who also recomputes the chain would need to defeat a signed head — that signed-head step is future hardening.)

How the chain works

event[0]  prev_hash = 000…0 (genesis)   entry_hash = H(0x01 ‖ prev ‖ H(0x00 ‖ content))
event[1]  prev_hash = event[0].entry_hash    entry_hash = H(… ‖ prev ‖ …)
event[2]  prev_hash = event[1].entry_hash    …
row.head_hash = last entry_hash   ← edit any past content ⇒ recomputed hashes diverge ⇒ DETECTED
Check it: python3 findings/evolution/shared_data/candidate_ledger.py --verify-chain · or /registry:verify-chain · also folded into /registry:doctor and /registry:validate.