This is the authoritative, dependency-sorted plan for driving the formal verification of the Ochránce estate to completion. It is the durable source of truth for the proof thread: it survives context compaction, so each working window starts from this file rather than from chat memory.
- 1. Scope & estate map
- 2. Decisions on record
- 3. Current proof state (ochrance, canonical)
- 4. The remaining proof program (dependency-sorted)
- 4.1. Stage 1 — Merkle closure + crypto-binding interface [model: Opus]
- 4.2. Stage 2 — Verify + Validator soundness [model: Opus → Sonnet if mechanical]
- 4.3. Stage 3 — Repair correctness (L3, linear types) [model: Opus]
- 4.4. Stage 4 — Completeness, binding discharge, write-up [model: Sonnet; Opus if binding is hard]
- 5. The IO↔pure bridge (estate-wide spine)
- 6. Build-with-proofs discipline
- 7. Optimisation ledger (against the proven backdrop)
- 8. Honestly-bounded items (NOT failures — boundaries by design)
- 9. Disposed tracks (handoff briefs)
- 10. Model guidance (per the thread’s operating model)
Three repositories are in scope. "Verification" means something different in each.
| Repo | What "verified" means | This thread executes? |
|---|---|---|
|
Idris2 dependent-type proofs (the canonical core). |
Yes — primary focus. |
|
Architecture + docs. Its |
Docs/Interface harvest only — core is deleted, not proven. |
|
ReScript edge gateway → migrating to Ephapax (replaces all ReScript). Type-safety and schema/property tests, not dependent types. |
No — disposed to a delegated session (see Disposed Tracks). Specs tracked here. |
| ID | Decision |
|---|---|
D1 — Converge on ochrance |
|
D2 — Crypto binding as a typed interface |
The security half of the Merkle argument (collision resistance) is modelled as
an Idris hypothesis |
D3 — svalinn: migrate before proving |
Prove where the language is final; migrate-then-prove where it is changing.
svalinn migrates ReScript → Ephapax — Ephapax replaces all the ReScript.
Its linear/exactly-once types make JWT/JTI single-use & revocation, OAuth
nonce/PKCE, and session/container lifecycle compile-time guarantees, and typed
boundary decoders eliminate the 20+ |
The core is clean: all 19 ochrance-core modules carry %default total (the
src/abi/ tree adds the ABI modules separately); zero
believe_me / assert_* / postulate / holes / partial on any proof symbol.
| Theorem | Statement (shape) |
|---|---|
|
inclusion-proof soundness; generic over the hash combiner |
|
|
|
supporting lemmas. |
|
constructor round-trip: |
|
reusable transport/append lemmas: |
|
root characterisation: |
|
Either-monad folds compute the pure spec on success; production |
|
binding discharged against the typed hypothesis |
|
the binding hypothesis HAS TEETH: a degenerate combiner (ignores its inputs) provably
fails |
|
validator soundness: |
|
hex codec structural soundness: |
|
verifier soundness: |
|
pure repair primitive correctness over |
|
verify↔Merkle wiring, three modes (generic→granular): root-equivalence (faithful
fingerprint, equal roots |
|
live root-verification soundness at the A2ML |
|
whole-manifest repair ⇒ verify: |
|
grammar invertibility: |
|
progressive-assurance threshold witness. |
ABI |
|
Each stage is sized to roughly one context window; the thread compacts at each boundary with this file as the hand-off. Watch-fors are things that may force a design change or a model downshift mid-stage.
-
[DONE — 1.1]
buildMerkleTree↔getLeafHashround-trip:getLeafHash (buildMerkleTree hs) (finToNat i) = Just (index i hs)(buildGetLeafinOchrance.Filesystem.MerkleBuild, on the reusableOchrance.Util.VectLemmaslemmas). Machine-checked, axiom-free, onmain. -
[DONE — 1.2] Root-fold law:
rootHashWith h (buildMerkleTree hs) = foldRoot h hs(rootFoldLaw+foldRootinOchrance.Filesystem.MerkleBuild) — the root is a deterministic, representation-independent fold over the leaves. Combiner-generic; prerequisite of the binding argument. Machine-checked, axiom-free. -
[DONE — 1.3] IO↔pure bridge (decompose): the pure
Either-monad mirrorsrootHashBytesE/verifyProofEprovably compute the spec —rootHashBytesE_spec:= Right (rootHashWith h t),verifyProofE_spec:= Right (verifyProofWith h …)whencfmodelsh— fully machine-checked ("modulo the Either plumbing"). The opaque-IO step (IO fold =pureof its pure mirror) is the sole assumed boundary, passed as an explicitreflecthypothesis (nopostulate/believe_me); the production theoremsrootHashBytesIO_spec/verifyProofIO_spechold conditionally on it.Ochrance.Filesystem.MerkleIO. -
[DONE — 1.4] D2:
CollisionResistant h(combiner injectivity) introduced andmerkleBindingdischarged against it — not merely stated. Equal leaf folds (roots) force equal leaves:foldRoot h xs = foldRoot h ys → xs = ys, withmerkleBindingTreethe built-tree-root corollary (viarootFoldLaw). The proof lifts combiner injectivity through the fold (Stage 1.2 is its prerequisite);CollisionResistant his the sole assumed hypothesis — nopostulate/believe_me— and the irreducible cryptographic trust root (Stage 4 isolates it, does not discharge it — pigeonhole-false; seeMerkleAssumption). New reusable lemmassplitAtEta/replaceInjinVectLemmas.Ochrance.Filesystem.MerkleBinding. Machine-checked (idris2 0.8.0,--total). This pulls the Stage 4 binding-discharge forward to its hypothesis.RESOLVED (was WATCH-FOR): the
replace-by-powerTwoSucctransport inbuildMerkleTreewas discharged without reshaping the builder — via theindexReplace/finToNatReplace/splitAtConcattransport-cancellation lemmas inVectLemmas. No API change was forced.
-
[DONE — 2.1] Validator soundness: a manifest accepted by
validateManifestsatisfies the structural invariants it checks — supported version, non-empty subsystem, and well-formed-hex ref hashes (All RefValid m.refs).ValidManifestis therefore a genuine validity witness, proved by inverting the Either-monad validation pipeline.validateManifestSoundinOchrance.A2ML.ValidatorProof(helperstraverseRefsSound/validRefSound). Machine-checked (idris2 0.8.0,--total), axiom-free.HONEST FORM (was WATCH-FOR, confirmed): invariants are stated at the decision (Bool) level —
isVersionSupported v = True,(sub == "") = False,isValidHexString h = True— not inverted into propositionalStringfacts (v = "0.1.0",Not (sub = "")):Stringequality is primitive, so the Bool form is the strongest honest statement. Two reduction subtleties recorded for reuse: (i)traverse_forEitherdesugars through<*>andRight () > yis onlymap id y(functor-identity *law, not definitional) — so invert by casing on the head outcome and the tail fold, keeping each step definitional; (ii)withabstracts only a hypothesis’s WHNF, so case onvalidateRef ref(exposed there), not the nestedisValidHexString.EXTENDED (2026-07-02): policy enforcement is now part of the proven surface.
validatePolicy— previously dead code in the production path (a manifest withrequire_sig = trueand no attestation passed) — is wired intovalidateManifest, andvalidateManifestSoundis a 4-tuple: acceptance also forcesvalidatePolicy m = Right (). New theorems, all machine-checked, axiom-free:requireSigNoAttestationRejected(the gate is provably wired in);staleManifestRejected(end-to-end:validatePolicyAtrejects a manifest older thanmax_age— proved by rewriting the goal along theparseTimestamp ts = Just issuedhypothesis, which unblocks both stuck case scrutinees at once; thewith-abstraction route fails here becausewithcannot rewrite an already-fixed hypothesis);checkFreshnessRejectsStale/checkFreshnessAcceptsFresh;hexAcceptSound(strengthened check: exactly 64 hex chars AND hex-only content — the old check accepted'.', empty and any-length strings) +hexWrongLengthRejected+hexEmptyRejected; and known-answer proofs for the total ISO-8601-subsetparseTimestamp(epoch = 0, modern date cross-checked, garbage and month-13 rejected). Freshness is clock-free in the pure layer (validatePolicyAttakesnow;validateManifestIOfetchesSystem.time) — IO↔pure-bridge style.CI-ENFORCED (2026-07-01): the entire ledger is now gated per PR — the
Idris2workflow builds every core module under--total(a broken proof is a red check) and runs the Idris→Zig FFI runtime test (tests/ffi/run_ffi_test.sh), so the production-Merkle-root-is-real-BLAKE3 fact is re-verified on every change. The three Idris test suites are fail-capable (exit 1) as of the same change.SIGNING CONVENTION v1 (2026-07-07):
serializeForSigningis now the canonical, delimited signing serialization — domain tagochrance-sign-v1, 8-byte big-endian length prefix on every variable-length field, count prefix on the ref list, presence byte on optionals — binding all semantic fields: version, subsystem, timestamp, refs, policy, and the attestation’s witness + pubkey (only the signature itself is excluded). The prior form concatenated bare version/subsystem/refs bytes, so (a) timestamp, policy and witness were tamperable on a "signed" manifest and (b) distinct manifests could serialize identically (boundary shiftab|c=a|bc). The positive path is now CI-enforced end-to-end: the Zig FFI gained a deterministic Ed25519 signer (ed25519_sign/ed25519_public_key_from_seed, KAT-tested in-module and in the dlopen link test), andtests/ffi/CryptoFFITest.idrsignsblake3(serializeForSigning m)with it, watchesvalidateManifestIOaccept the manifest, and confirms tampering timestamp / witness / ref digest each flips the result toSignatureVerificationFailed. Any layout change is a breaking convention change and must bump the domain tag. -
[DONE — 2.2 (inversion); merkle-wiring deferred]
verifysoundness. LiftedverifyRefsHelper/parseBlockIdxout of the instancewhereto top-levelpublic export, then provedverifyRefsSound:verifyRefsHelper fs refs = Right () → All (RefMatches fs) refs— acceptance ⇒ every ref names an in-range block whose stored hash equals the ref’s (Bool-levelh == ref.hash = True), by inverting the four per-ref guards (name-parse, range, block-present, hash-equal).Ochrance.Filesystem.VerifyProof. Machine-checked, axiom-free.WIRED (Stage 2.4), three modes generic→granular in
Ochrance.Filesystem.VerifyMerkle: (1) root-equivalence —rootFaithfulproves the root is a FAITHFUL fingerprint (equal roots<→equal leaves; forward =merkleBindingTree/CollisionResistant h, backward = congruence) androotVerifySoundis the security reading (matching committed root ⇒ identical blocks); (2) inclusion-proof —inclusionVerifySoundre-exposesmerkleCorrect(1.1) as a per-leaf verification guarantee; (3) live bridge —hashToBytesdecodes A2MLHash→ MerkleHashBytesfor snapshot-root verification, composing withrootVerifySound.SOUNDNESS PROVEN (
merkleRootVerifyHashSound): replacing the liveHash-basedverifyRefsHelperwith a root comparison is now sound at theHashlevel - two block-hash vectors that decode and yield equal Merkle roots are equal - carryingrootVerifySoundup across the decoder bridge (mapDecodeInjective). Two named boundaries:CollisionResistant h(1.4) andDecodeInjective dec(the hex wall, 2.3); stated for an arbitrarydec, instantiated athashToBytes.DONE (plumbing):
Ochrance.Filesystem.VerifyRoot-padToLength+nextPow2Exp
layoutLeavespad an arbitrary-length block list to a power-of-two leafVect;verifyByRoot/verifyByRootHashbuild the tree and compare roots;fsBlockHashes+verifySnapshotRootare the runtime path againstFSSnapshot.rootHash. Executable, total; its accept-on-match soundness ismerkleRootVerifyHashSound. -
[DONE — 2.3] Hex codec structural round-trip. The full
hexStringToBytes (bytesToHex bs) = Just bscrosses two primitive walls —unpack∘pack(no equational theory; the lexer-round-trip wall) and per-byteBits8div/mod. The honest, axiom-free core is now proven:parsePairsRoundtripshowsparsePairs (bytesToHexChars bs) = Just bs—parsePairsinvertsbytesToHexCharsexactly — given the isolated per-byte hypothesisHexByteRoundtrip(theBits8boundary, named not faked, IO↔pure-bridge style). Required liftingtoPair→toHexPairandbytesToHexCharsto top-level (the builder recurses explicitly, dodging the non-reducingconcatMapMonoid layer — same hazard astraverse_) and exposingparsePairs.Ochrance.Util.HexProof. Machine-checked.
RESOLVED (was PRECONDITION): the pure repair core is now factored out —
repairBlockPure : FSState → BlockIndex → Hash → FSState installs a hash in the
map, and the IO repairBlock is repairBlockPure + the range check (IO↔pure
pattern). Because FSState’s verifiable content is its hash map (it carries no
separate block data), this is the genuine repair semantics, so the theorems are
well-founded, not vacuous.
-
[DONE — 3.1] Pure repair primitive correctness, machine-checked (
Ochrance.Filesystem.RepairProof, axiom-free):repairBlockSets(the repaired index now holds the new hash),repairBlockPreserves(every other index untouched),repairBlockNumBlocks(block count preserved), andrepairBlockIdempotent(repairing the same (index, hash) twice = once) — the ledger’s "idempotence as a proof", delivered. The index test isNat==(structural —eqNatReflTrue/neqNatFalse), so wall-free, unlike the primitiveHash==. -
[DONE — 3.2] Whole-manifest repair ⇒ verify:
verifyRefsHelper (repairRefsPure s refs) refs = Right ()(Ochrance.Filesystem.RepairVerify,repairThenVerify). Composes 2.2’s verifier with the 3.1 lemmas via completeness (verifyRefsComplete, the converse ofverifyRefsSound) ∘ repair-consistency (repairRefsConsistent). The two needed boundaries are named, not faked: preconditionGoodRefs(ref names parse to distinct in-range indices — else a later repair clobbers an earlier ref’s block, consumed in the no-clobber lemmarepairRefsPurePreserves), and the isolatedHash-reflexivity hypothesishashRefl : (h == h) = True(the primitive==wall, as in `merkleCorrect’s residual step). Machine-checked, totality-clean. -
Harvest framework’s mode-indexed Interface; state the
VerifiedSubsystemlaw and prove the FSState instance satisfies it.WATCH-FOR: may need proof witnesses threaded through the
1-quantified API — possible signature changes toRepair.idr.
-
Merkle completeness (converse of soundness): in-range leaf ⇒ a proof exists.
-
[DONE — D2] CR isolated, not discharged: the binding argument is proven against
CollisionResistant h(Stage 1.4). The hypothesis itself CANNOT be discharged — full injectivity is pigeonhole-false for a compressing combiner — so Stage 4 isolates it as the irreducible cryptographic trust root and proves it has teeth (constNotCollisionResistant,MerkleAssumption). Optional follow-on engineering: wire the real Zig/FFI combiner in and declare CR as its explicit assumption. -
Progressive monotonicity: the remaining
SatisfiesMinimumcases (allRefl). -
Final ledger pass; thesis-aligned summary (ICFP/PLDI/SOSP framing).
CLEAR = every intended theorem proven or honestly bounded (primitive walls documented, never faked), disposed tracks handed off, PRs landed.
The same device recurs at every IO boundary — Merkle (1.3), Verify (2.2), Repair (3), signatures (4 / bounded). The shape is always:
pure spec (proven) --[extensional-equality lemma]--> IO production path
(modulo `Either`
allocation-failure +
a typed crypto hypothesis)
Treating this as one architectural pattern — not four ad-hoc stage items — is what
lets the proven backdrop reach production code without ever faking the FFI. The
typed crypto hypotheses (CollisionResistant h; hashPairBlake3 a b equals the
spec combiner) are introduced in Stage 1.4 and discharged-or-assumed explicitly,
never silently. It is also the mechanism that licenses optimisation (below).
The invariant: code never outstrips proofs. Operationally —
-
A correctness-claiming
public exportsymbol lands only when (a) it carries its lemma in the same PR, or (b) it is explicitly-- UNPROVEN:-marked with a tracking issue, or (c) it is a documented honestly-bounded wall. No silent (d). -
The
--totalCI build is the totality proof (already enforced) — a green build is the floor, not the ceiling. -
No proving stubs. If an implementation is a placeholder (Repair today), make it real or model it purely first — never point a theorem at a no-op.
-
This ledger’s proven-surface table is the source of truth;
README/TOPOLOGYmust not claim beyond it.
Governing principle: the proven surface is exactly the code you may optimise — the
proof is the optimisation’s regression contract. Keep the simple reference R
(proven), introduce optimised F, discharge F x = R x, and every theorem about R
transports to F by rewrite. Never optimise unproven code (nothing guards it).
| Target | Optimisation | Guard | Status |
|---|---|---|---|
|
bottom-up O(n) fold from the flat vector (vs. per-level |
|
unlocked now |
|
thread a |
|
unlocked now |
|
difference-list / vector path (vs. List append per step) |
|
unlocked now |
root combiner |
real BLAKE3 in IO (vs. abstract |
|
unlocked (Stage 1.3) |
A2ML production parser |
any fast String parser |
|
bounded (prim. wall) |
Repair (incremental / CoW) |
touch only broken blocks |
|
after Stage 3 |
-
Production-pipeline round-trip (
parse . lex . serialize = Right m) cannot carry a compile-time theorem:pack/unpack/parseIntegerare primitives with no equational theory. The honest guarantee isroundtripManifestover a reference token codec, complemented byroundtripPropertyat runtime. -
root == rootBool step inmerkleCorrect: the propositional digest equality is the strongest honest statement; discharging the residual primitive-Bits8==would need an unsafe reflexivity axiom.
These are not this thread’s work. Each is a brief for a delegated session, disposed at Compaction 1. Specs live here so nothing is lost.
PRECONDITION: language migration precedes proof (Decision D3). The first
deliverable is a migration map (the critical chain of blockers) — handed to an
offline Claude with hyperpolymath/ephapax access, targeting
svalinn/docs/ephapax-migration/BLOCKER-LINEAGE.adoc. ROOT of that chain, and the
one thing this session could not settle (ephapax is out of scope here): whether
Ephapax is yet an implementable application language (compiler, runtime,
HTTP/async I/O, JSON, fetch, crypto, FFI) or still a proof-level calculus.
-
Migrate ReScript → Ephapax (gateway, auth, policy, validation, MCP, vörðr, compose; ~27
src/modules + theui/ReScript frontend). Typed boundary decoders make the 20+Obj.magiccasts in security paths impossible. -
Ephapax linear tokens for exactly-once resources: JWT/JTI single-use + the revocation ledger (fixes the
hasRevocationList = false // TODOhazard by construction), OAuth nonce/PKCE, session/container lifecycle, vörðr delegation. -
Unblock CI: the 53 ReScript unit/security tests don’t execute under Deno (
@rescript/coreresolution) — they vanish with the migration; ensure the Ephapax suite runs in CI. (svalinnmainCI is currently red on pre-existing ReScript breakage — do not fix it in ReScript; it is being replaced.) -
Specs to discharge after migration (language-agnostic): policy determinism;
allow ∩ denycomposition + monotonicity; JWT single-use & revocation; no unchecked boundary casts; JSON-Schema conformance of all 9 gateway types. -
Planned SPARK properties (
cerro-torre-integration.adoc §6.2): attestation sig, key lookup, threshold sig, log inclusion, policy eval.DEPENDENCY: Ephapax has 3 Admitted (Coq) — svalinn’s linear guarantees inherit those holes until closed. Track upstream.
DISCHARGES: svalinn security issue #13 (19 Critical/High panic-attack findings —
decodeJwt()withoutjwtVerify(),JSON.parseExn) is addressed structurally by this migration (verified JWT + typed deserialisation), not by patching ReScript.RE-ENTRY: the outbound charter now lives at
svalinn/docs/ephapax-migration/HANDOFF.adoc; when this track returns, readdocs/AFTER-MIGRATION.adoc(the round-trip closure) before resuming the campaign.
-
Delete
ochrance-framework/ochrance-core; make the repo depend on / reference the canonical ochrance core. -
Harvest the mode-indexed
VerifiedSubsystemInterface into ochrance (Stage 3). -
Fix the weak spots that should not be carried over:
decodeSnapshotalways returnsNothing(repair unreachable);signatureValid : BoolandallPresent : Boolare unguarded runtime flags; no totality gate in CI. -
Keep and maintain the docs — they are the framework repo’s real value.
-
blake3Hashinsrc/abi/Ochrance/ABI/Foreign.idriscovering+ a stub returningreplicate 32 0— wire it to the real ZiglibochranceBLAKE3. -
ECHIDNA FFI is entirely stubbed (
echidnaProvereturnsLeft "FFI not yet implemented").
| Model | Use |
|---|---|
Opus 4.8 |
Proof discovery — "is this provable, what is the shape": Stages 1, 3, and any novel theorem or structural-wall reasoning. |
Sonnet 4.6 |
Proof mechanisation (known shape), refactors, tests, and the disposed-track application work (svalinn Ephapax migration, framework, ABI). |
Haiku 4.5 |
Grunt sweeps — suites, grep, SPDX/format, CI watch. |
RULE: when a stage’s remaining work is all mechanical, downshift this thread to Sonnet to conserve Opus budget; when a research-grade wall appears, pause for the design conversation rather than pushing proofs.