Skip to content

Commit 7e47f66

Browse files
claudehyperpolymath
authored andcommitted
proof(repair): Stage 3.2 — whole-manifest repair ⇒ verify (repairThenVerify)
End-to-end guarantee tying Stage 2.2 (verifier soundness) to Stage 3.1 (pure repair): after repairing every ref of a manifest, the verifier accepts it — verifyRefsHelper (repairRefsPure s refs) refs = Right () New module Ochrance.Filesystem.RepairVerify: - repairRefsPure — pure whole-manifest repair; folds the 3.1 primitive repairBlockPure (installs each ref's hash at its parsed index). - verifyRefsComplete (Lemma A) — completeness, the exact converse of 2.2's verifyRefsSound: All (RefMatches fs) refs ⇒ verifier accepts. - repairRefsConsistent (Lemma B) — repair establishes the match witness for every ref; head hash installed (repairBlockSets) and protected from later repairs (repairRefsPurePreserves, the no-clobber lemma where distinctness is consumed). - repairThenVerify — Stage 3.2, Lemma B ∘ Lemma A. Two boundaries named, never faked (house style of CollisionResistant/HexByteRoundtrip): - GoodRefs — ref names parse to DISTINCT in-range indices (without distinctness a later repair clobbers an earlier ref, so the theorem would be false); - hashRefl : (h == h) = True — reflexivity of the primitive Hash/String ==, the same wall as merkleCorrect's residual step (Nat reflexivity stays structural). Also exports neqNatFalse from RepairProof (needed by the no-clobber lemma). Verified: idris2 0.8.0, --build (--total), 27/27 modules, no warnings, axiom-free. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011z2t8zAxfcCNLJzU7YdpBQ
1 parent ce195a5 commit 7e47f66

5 files changed

Lines changed: 208 additions & 9 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ project = "ochrance"
77
version = "0.1.0"
88
last-updated = "2026-06-18"
99
status = "active"
10-
session = "machine-readable checkpoint — 2026-06-18 (Stage 1 complete; Stage 2 core + 2.4 verify↔Merkle wiring; Stage 3.1 pure repair correctness)"
10+
session = "machine-readable checkpoint — 2026-06-18 (Stage 1 complete; Stage 2 core + 2.4 verify↔Merkle wiring; Stage 3.1 pure repair + 3.2 repair⇒verify)"
1111

1212
[project-context]
1313
name = "Ochránce"
@@ -31,6 +31,7 @@ theorems = [
3131
"parsePairsRoundtrip (Stage 2.3) — hex codec structural soundness: parsePairs (bytesToHexChars bs) = Just bs, given isolated per-byte Bits8 hypothesis (pack/unpack wall explicit)",
3232
"verifyRefsSound (Stage 2.2) — verifier soundness: verifyRefsHelper fs refs = Right () ⇒ All (RefMatches fs) refs (four-guard per-ref inversion; merkle-root wiring deferred as architectural)",
3333
"repairBlock{Sets,Preserves,NumBlocks,Idempotent} (Stage 3.1) — pure repair primitive correctness over repairBlockPure: installs hash at index, preserves others + count, idempotent (wall-free Nat == via eqNatReflTrue/neqNatFalse)",
34+
"repairThenVerify (Stage 3.2) — whole-manifest repair ⇒ verify: verifyRefsHelper (repairRefsPure s refs) refs = Right (). Lemma A verifyRefsComplete (completeness, converse of 2.2) ∘ Lemma B repairRefsConsistent (install+no-clobber via repairBlockSets/repairRefsPurePreserves). Honest hyps: GoodRefs (distinct in-range names) + hashRefl (primitive Hash == reflexivity)",
3435
"rootFaithful / rootVerifySound / inclusionVerifySound / hashToBytes (Stage 2.4) — verify↔Merkle wiring, 3 modes generic→granular: root-equivalence (faithful fingerprint via merkleBindingTree), inclusion-proof (merkleCorrect per-leaf), live Hash↔HashBytes bridge for snapshot-root",
3536
"roundtripManifest (+ algoRT/modeRT/refsRT/mpolRT codecs) — decodeManifest (encodeManifest m) = Just m",
3637
"SatisfiesMinimum / attestedSatisfiesLax — progressive-assurance threshold witness",
@@ -59,7 +60,8 @@ issues = [
5960
actions = [
6061
"Stage 1 COMPLETE — 1.1 buildGetLeaf, 1.2 rootFoldLaw, 1.3 IO↔pure bridge, 1.4 merkleBinding/CollisionResistant — all machine-checked (idris2 0.8.0, --total)",
6162
"Stage 2 provable core COMPLETE — 2.1 validateManifestSound, 2.2 verifyRefsSound, 2.3 parsePairsRoundtrip; all machine-checked. DEFERRED (architectural decision): wire verify to merkleCorrect — the verify path builds no Merkle tree today",
62-
"Stage 3.1 DONE — pure repair primitive correctness (repairBlockPure factored out; sets/preserves/numBlocks/idempotent machine-checked). NEXT 3.2: whole-manifest repair ⇒ verifyRefsHelper passes (needs distinct-in-range ref-name precondition + isolated Hash-reflexivity hypothesis)",
63+
"Stage 3.1 DONE — pure repair primitive correctness (repairBlockPure factored out; sets/preserves/numBlocks/idempotent machine-checked)",
64+
"Stage 3.2 DONE — whole-manifest repair ⇒ verify (repairThenVerify, module RepairVerify). repairRefsPure folds repairBlockPure; proof = verifyRefsComplete ∘ repairRefsConsistent; GoodRefs precondition + hashRefl hypothesis, both named. NEXT: architectural live-verifier Merkle redesign (verifyRefsHelper builds a tree via hashToBytes + rootVerifySound; bounded by hex walls + power-of-2 layout)",
6365
"Stage 2.4 DONE — verify↔Merkle proof-level wiring (rootFaithful/rootVerifySound): the root is a faithful fingerprint of the blocks (binding). REMAINING: connect to the live Hash-based verifyRefsHelper via hex Hash<->HashBytes conversion (2.3-bounded) + power-of-two layout (verify-path change, not a proof)",
6466
"Stage 3.2 (next provable): whole-manifest repairPure ⇒ verifyRefsHelper accepts (distinct-in-range ref-name precondition + isolated Hash-reflexivity hypothesis)",
6567
"Remove Idris-side crypto stubs and build + link libochrance.so into the flow (unblocks the crypto-integrity claim)",

docs/PROOFS.adoc

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ The core is *clean*: all 19 `ochrance-core` modules carry `%default total` (the
121121
inclusion-proof verification (`merkleCorrect` as a per-leaf guarantee); and the live
122122
`Hash`↔`HashBytes` decode bridge for snapshot-root verification. Binding (1.4) and
123123
inclusion soundness (1.1) carried into the verify use-case.
124+
| `repairThenVerify` (+ `verifyRefsComplete`, `repairRefsConsistent`) (Stage 3.2)
125+
| whole-manifest repair ⇒ verify: `verifyRefsHelper (repairRefsPure s refs) refs =
126+
Right ()`. Lemma A `verifyRefsComplete` (completeness — exact converse of 2.2) ∘
127+
Lemma B `repairRefsConsistent` (repair installs each ref's hash and protects it from
128+
later repairs, via `repairBlockSets` / `repairRefsPurePreserves`). Honest hypotheses:
129+
`GoodRefs` (distinct in-range parseable names) and `hashRefl` (primitive `Hash` `==`
130+
reflexivity).
124131
| `roundtripManifest` + sub-codecs (`algoRT`, `modeRT`, `refsRT`, `mpolRT`, …)
125132
| grammar invertibility: `decodeManifest (encodeManifest m) = Just m`.
126133
| `SatisfiesMinimum` / `attestedSatisfiesLax`
@@ -242,13 +249,16 @@ well-founded, not vacuous.
242249
ledger's "idempotence as a proof", delivered. The index test is `Nat` `==`
243250
(structural — `eqNatReflTrue` / `neqNatFalse`), so wall-free, unlike the primitive
244251
`Hash` `==`.
245-
. *[3.2 — next]* Whole-manifest repair ⇒ verify:
246-
`verifyRefsHelper (repairPure s m) m.refs = Right ()`. Builds on 2.2's
247-
`verifyRefsHelper` and the 3.1 lemmas, but needs (a) a precondition that ref names
248-
parse to *distinct* in-range indices (so the rebuilt map is per-ref consistent),
249-
and (b) the isolated `Hash`-reflexivity hypothesis `(h == h) = True` — the same
250-
primitive `==` wall as `merkleCorrect`'s residual step — named explicitly, never
251-
faked.
252+
. *[DONE — 3.2]* Whole-manifest repair ⇒ verify:
253+
`verifyRefsHelper (repairRefsPure s refs) refs = Right ()`
254+
(`Ochrance.Filesystem.RepairVerify`, `repairThenVerify`). Composes 2.2's verifier
255+
with the 3.1 lemmas via completeness (`verifyRefsComplete`, the converse of
256+
`verifyRefsSound`) ∘ repair-consistency (`repairRefsConsistent`). The two needed
257+
boundaries are named, not faked: precondition `GoodRefs` (ref names parse to
258+
*distinct* in-range indices — else a later repair clobbers an earlier ref's block,
259+
consumed in the no-clobber lemma `repairRefsPurePreserves`), and the isolated
260+
`Hash`-reflexivity hypothesis `hashRefl : (h == h) = True` (the primitive `==` wall,
261+
as in `merkleCorrect`'s residual step). Machine-checked, totality-clean.
252262
. Harvest framework's mode-indexed Interface; state the `VerifiedSubsystem` law and
253263
prove the FSState instance satisfies it.
254264
+

ochrance-core/Ochrance/Filesystem/RepairProof.idr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ repairBlockNumBlocks s i h = Refl
5555
||| `Nat` inequality reflects as `== = False` (structural counterpart of the
5656
||| primitive-equality wall). Used to drive `repairBlockPreserves` on the `j /= i`
5757
||| branch of idempotence.
58+
export
5859
neqNatFalse : (m, n : Nat) -> Not (m = n) -> (m == n) = False
5960
neqNatFalse Z Z ctra = absurd (ctra Refl)
6061
neqNatFalse Z (S _) _ = Refl
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
||| SPDX-License-Identifier: MPL-2.0
2+
|||
3+
||| Ochrance.Filesystem.RepairVerify - Stage 3.2: whole-manifest repair => verify.
4+
|||
5+
||| The end-to-end guarantee that ties Stage 2.2 (verifier soundness) to Stage 3.1
6+
||| (pure repair): after repairing every ref of a manifest, the verifier ACCEPTS
7+
||| that manifest - `verifyRefsHelper (repairRefsPure s refs) refs = Right ()`.
8+
|||
9+
||| `repairRefsPure` folds the 3.1 single-block primitive `repairBlockPure` over the
10+
||| refs, installing each ref's hash at its parsed index. The proof factors as:
11+
|||
12+
||| * `verifyRefsComplete` (Lemma A) - COMPLETENESS, the exact converse of 2.2's
13+
||| `verifyRefsSound`: if every ref matches the state (`All (RefMatches fs)`), the
14+
||| verifier accepts. Drives the four guards forward from the witness equalities.
15+
||| * `repairRefsConsistent` (Lemma B) - repair ESTABLISHES that match witness for
16+
||| every ref, via `repairBlockSets` (the head's index now holds its hash) and
17+
||| `repairRefsPurePreserves` (no later repair clobbers it - this is where
18+
||| distinctness is consumed).
19+
|||
20+
||| Two boundaries are named explicitly, never faked (the house style of
21+
||| `CollisionResistant` / `HexByteRoundtrip`):
22+
||| * `hashRefl : (h : Hash) -> (h == h) = True` - reflexivity of the PRIMITIVE
23+
||| `Hash`/`String` `==` (the same wall as `merkleCorrect`'s residual `root==root`
24+
||| step; `Nat` reflexivity, by contrast, is structural - `eqNatReflTrue`);
25+
||| * `GoodRefs` - the precondition that ref names parse to DISTINCT in-range
26+
||| indices. Without distinctness a later repair would overwrite an earlier ref's
27+
||| block, so the theorem would be false; it is a genuine hypothesis, not a dodge.
28+
|||
29+
||| No `believe_me`, no `assert_total`, no `postulate`.
30+
module Ochrance.Filesystem.RepairVerify
31+
32+
import Data.List.Quantifiers
33+
34+
import Ochrance.A2ML.Types
35+
import Ochrance.Framework.Error
36+
import Ochrance.Filesystem.Types
37+
import Ochrance.Filesystem.Verify
38+
import Ochrance.Filesystem.VerifyProof
39+
import Ochrance.Filesystem.Repair
40+
import Ochrance.Filesystem.RepairProof
41+
42+
%default total
43+
44+
--------------------------------------------------------------------------------
45+
-- Pure whole-manifest repair
46+
--------------------------------------------------------------------------------
47+
48+
||| Repair every ref of a manifest: install each ref's hash at its parsed block
49+
||| index (refs whose name does not parse are skipped - `GoodRefs` rules that out).
50+
||| Folds the Stage 3.1 primitive `repairBlockPure`.
51+
public export
52+
repairRefsPure : FSState -> List Ref -> FSState
53+
repairRefsPure s [] = s
54+
repairRefsPure s (ref :: refs) =
55+
case parseBlockIdx ref.name of
56+
Nothing => repairRefsPure s refs
57+
Just idx => repairRefsPure (repairBlockPure s idx ref.hash) refs
58+
59+
--------------------------------------------------------------------------------
60+
-- Precondition: distinct, in-range, parseable ref names
61+
--------------------------------------------------------------------------------
62+
63+
||| "This ref's name does NOT parse to index `i`." The atom of distinctness.
64+
public export
65+
NotParsedAs : BlockIndex -> Ref -> Type
66+
NotParsedAs i ref = Not (parseBlockIdx ref.name = Just i)
67+
68+
||| Every ref name parses to a distinct, in-range block index. Indexed by the block
69+
||| count (a `Nat`) rather than the whole state, since repair preserves the count -
70+
||| so the same `GoodRefs` serves the original and every repaired state.
71+
public export
72+
data GoodRefs : Nat -> List Ref -> Type where
73+
GoodNil : GoodRefs nb []
74+
GoodCons : {0 ref : Ref} -> {0 refs : List Ref} ->
75+
(idx : BlockIndex) ->
76+
parseBlockIdx ref.name = Just idx ->
77+
(idx >= nb) = False ->
78+
All (NotParsedAs idx) refs ->
79+
GoodRefs nb refs ->
80+
GoodRefs nb (ref :: refs)
81+
82+
--------------------------------------------------------------------------------
83+
-- Lemma A: completeness (converse of Stage 2.2)
84+
--------------------------------------------------------------------------------
85+
86+
||| COMPLETENESS: if every ref matches the filesystem state, the verifier accepts.
87+
||| The exact converse of `verifyRefsSound` - drives the four guards forward from
88+
||| each ref's `RefMatches` witness (name-parse, range, block-present, hash-equal).
89+
export
90+
verifyRefsComplete : (fs : FSState) -> (refs : List Ref) ->
91+
All (RefMatches fs) refs -> verifyRefsHelper fs refs = Right ()
92+
verifyRefsComplete fs [] [] = Refl
93+
verifyRefsComplete fs (ref :: rs) ((idx ** (pi, rng, (h ** (bh, heq)))) :: rest) =
94+
rewrite pi in
95+
rewrite rng in
96+
rewrite bh in
97+
rewrite heq in
98+
verifyRefsComplete fs rs rest
99+
100+
--------------------------------------------------------------------------------
101+
-- Repair structural lemmas
102+
--------------------------------------------------------------------------------
103+
104+
||| Whole-manifest repair preserves the block count (folds 3.1's
105+
||| `repairBlockNumBlocks`).
106+
export
107+
repairRefsPureNumBlocks : (s : FSState) -> (refs : List Ref) ->
108+
numBlocks (repairRefsPure s refs) = numBlocks s
109+
repairRefsPureNumBlocks s [] = Refl
110+
repairRefsPureNumBlocks s (ref :: rs) with (parseBlockIdx ref.name)
111+
repairRefsPureNumBlocks s (ref :: rs) | Nothing = repairRefsPureNumBlocks s rs
112+
repairRefsPureNumBlocks s (ref :: rs) | Just idx =
113+
repairRefsPureNumBlocks (repairBlockPure s idx ref.hash) rs
114+
115+
||| Repairing a list whose parsed indices all differ from `i` leaves block `i`
116+
||| untouched - the no-clobber lemma. Folds 3.1's `repairBlockPreserves`; the per-
117+
||| step inequality comes from `NotParsedAs` via `neqNatFalse`.
118+
export
119+
repairRefsPurePreserves : (s : FSState) -> (refs : List Ref) -> (i : BlockIndex) ->
120+
All (NotParsedAs i) refs ->
121+
blockHash (repairRefsPure s refs) i = blockHash s i
122+
repairRefsPurePreserves s [] i _ = Refl
123+
repairRefsPurePreserves s (ref :: rs) i (notHere :: notRest) with (parseBlockIdx ref.name)
124+
repairRefsPurePreserves s (ref :: rs) i (notHere :: notRest) | Nothing =
125+
repairRefsPurePreserves s rs i notRest
126+
repairRefsPurePreserves s (ref :: rs) i (notHere :: notRest) | Just j =
127+
-- `with` has refined `notHere : Not (Just j = Just i)`
128+
let iNotJ : Not (i = j)
129+
iNotJ = \p => notHere (cong Just (sym p))
130+
in trans (repairRefsPurePreserves (repairBlockPure s j ref.hash) rs i notRest)
131+
(repairBlockPreserves s j ref.hash i (neqNatFalse i j iNotJ))
132+
133+
||| Unfold one step of `repairRefsPure` once the head ref's index is known.
134+
export
135+
repairRefsUnfold : (s : FSState) -> (ref : Ref) -> (refs : List Ref) ->
136+
(idx : BlockIndex) -> parseBlockIdx ref.name = Just idx ->
137+
repairRefsPure s (ref :: refs) = repairRefsPure (repairBlockPure s idx ref.hash) refs
138+
repairRefsUnfold s ref refs idx pi = rewrite pi in Refl
139+
140+
--------------------------------------------------------------------------------
141+
-- Lemma B: repair establishes the match witness
142+
--------------------------------------------------------------------------------
143+
144+
||| Repair ESTABLISHES consistency: after `repairRefsPure`, every ref matches the
145+
||| repaired state. The head's hash is installed (`repairBlockSets`) and protected
146+
||| from later repairs (`repairRefsPurePreserves`, fed the head's distinctness); the
147+
||| tail is the induction hypothesis (the repaired state's count equals the
148+
||| original's, definitionally, so the same `GoodRefs` carries through). The hash
149+
||| equality of each ref against itself is the named `hashRefl` boundary.
150+
export
151+
repairRefsConsistent : (s : FSState) -> (refs : List Ref) ->
152+
GoodRefs (numBlocks s) refs ->
153+
(hashRefl : (h : Hash) -> (h == h) = True) ->
154+
All (RefMatches (repairRefsPure s refs)) refs
155+
repairRefsConsistent s [] GoodNil hashRefl = []
156+
repairRefsConsistent s (ref :: rs) (GoodCons idx pi rng notI good') hashRefl =
157+
let s' : FSState
158+
s' = repairBlockPure s idx ref.hash
159+
bhF : blockHash (repairRefsPure s' rs) idx = Just ref.hash
160+
bhF = trans (repairRefsPurePreserves s' rs idx notI)
161+
(repairBlockSets s idx ref.hash)
162+
rngF : (idx >= numBlocks (repairRefsPure s' rs)) = False
163+
rngF = rewrite repairRefsPureNumBlocks s' rs in rng
164+
headW : RefMatches (repairRefsPure s' rs) ref
165+
headW = (idx ** (pi, rngF, (ref.hash ** (bhF, hashRefl ref.hash))))
166+
tailW : All (RefMatches (repairRefsPure s' rs)) rs
167+
tailW = repairRefsConsistent s' rs good' hashRefl
168+
in rewrite repairRefsUnfold s ref rs idx pi in (headW :: tailW)
169+
170+
--------------------------------------------------------------------------------
171+
-- Stage 3.2: whole-manifest repair => verify
172+
--------------------------------------------------------------------------------
173+
174+
||| STAGE 3.2: repairing a manifest's refs makes the verifier accept it. Composes
175+
||| Lemma B (repair => match witnesses) with Lemma A (witnesses => acceptance).
176+
||| Honest hypotheses: `GoodRefs` (distinct in-range parseable names) and `hashRefl`
177+
||| (primitive `Hash` `==` reflexivity).
178+
export
179+
repairThenVerify : (s : FSState) -> (refs : List Ref) ->
180+
GoodRefs (numBlocks s) refs ->
181+
(hashRefl : (h : Hash) -> (h == h) = True) ->
182+
verifyRefsHelper (repairRefsPure s refs) refs = Right ()
183+
repairThenVerify s refs good hashRefl =
184+
verifyRefsComplete (repairRefsPure s refs) refs
185+
(repairRefsConsistent s refs good hashRefl)

ochrance.ipkg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ modules = Ochrance.A2ML.Types
3434
, Ochrance.Filesystem.VerifyMerkle
3535
, Ochrance.Filesystem.Repair
3636
, Ochrance.Filesystem.RepairProof
37+
, Ochrance.Filesystem.RepairVerify
3738
, Ochrance.FFI.Echidna
3839

3940
opts = "--total"

0 commit comments

Comments
 (0)