Skip to content

Commit 6806b93

Browse files
proofs(idris2): Q5 option 3 — propositional Equiv migration (closes equivTrans) (#136)
## Summary Builds on PR #133 (Q1-C primitive-eq axiom pilot). Migrates \`Filesystem.Model.equiv\` from a boolean \`Foldable\`-backed shape to a propositional \`Data.List.Quantifiers.All\` / \`Data.List.Elem.Elem\` shape, closing **equivTrans** (the hole that PR #133's pilot attempted but couldn't reach). ## Background The Q1-C pilot in #133 unblocked primitive-eq leaf reflexivity via 2 named \`axStringEqRefl\` / \`axBits8EqRefl\` axioms, sufficient to close \`equivRefl\`. The same-session attempt to also close \`equivTrans\` failed: in Idris2 0.8.0, \`all p (x :: rs)\` does NOT reduce to \`(p x && all p rs)\` by \`Refl\` — the elaborator produces a \`foldl\`-shaped term via \`foldMap @{All}\` that no straightforward rewrite can destructure. Q5 option 3 (per the design report from this same session) replaces the boolean form entirely. ## What's in this PR ### Model.idr \`\`\`idris public export data Equiv : Filesystem -> Filesystem -> Type where MkEquiv : All (\\e => Elem e (entries fs2)) (entries fs1) -> All (\\e => Elem e (entries fs1)) (entries fs2) -> Equiv fs1 fs2 \`\`\` \`Data.List.Quantifiers.All\` reduces structurally on cons by definition — no foldMap, no foldl wall. | Theorem | Closure | |---|---| | \`equivRefl\` | Structural induction with \`Here\` at the head + local \`allThere\` helper for the recursive case | | \`equivSym\` | Constructor swap of the two \`All\` witnesses (was \`andCommutative\` rewrite in boolean form) | | \`equivTrans\` | \`mapProperty (\\e => indexAll e fwd23) fwd12\` + symmetric backward direction. **Closes #119 Cat-B for equivTrans.** | ### Operations.idr \`cnoWriteSameContent\`'s signature migrated from boolean to propositional. Body **stays as \`?cnoWriteSameContentProof\`** with an extended comment documenting a NEW finding: under the duplicate-keyed entries model, the theorem is refutable even with the propositional shape, because \`getFileContent\` only constrains the FIRST entry at \`p\`. Closure needs a \"no duplicate keys\" invariant on \`Filesystem\`. Recorded in PROOF-NEEDS.md. ### PROOF-NEEDS.md * Hole tally: 15 → 14 * \`Model.idr\` row: 0 holes remaining (was 1) * \`Operations.idr\` row: notes the propositional shape on \`cnoWriteSameContent\` ## What does NOT close * \`cnoWriteSameContent\` — see above; new model-invariant finding documented * 7 reversibility theorems in Operations.idr + 4 in Composition.idr + 3 RMO non-theorems-as-stated (#129/#130/#131) These are tracked independently and unaffected by this change. ## Axiom inventory * \`believe_me\` count in \`Axioms.idr\`: **2** (unchanged from pilot) * \`IDRIS2_AXIOMS.a2ml\` entries: **2** (unchanged) * CI guard passes * \`pathEqRefl\` / \`fsEntryEqRefl\` / \`entryEqRefl\` kept in Model.idr — unused for \`Equiv\` now, but available for future proofs that need leaf-level \`(==) = True\` ## Test plan - [x] \`idris2 --build valence-shell.ipkg\` exit 0 (all 5 modules clean) - [x] \`.github/scripts/check-idris2-believe-me.sh\` passes - [x] \`grep -c '?equivTrans' Model.idr\` → 0 - [x] \`grep -c '?equivRefl' Model.idr\` → 0 - [x] Model.idr has zero open \`?holes\` - [ ] CI \`idris-verification.yml\` green - [ ] Reviewer confirms the cnoWriteSameContent model-invariant finding ## Stacking Depends on PR #133 (which lands the pilot axiom infrastructure). Once #133 merges, this PR rebases trivially onto main. If #133 doesn't land, this PR can stand on its own by dropping the \`Filesystem.Axioms\` import from \`Model.idr\` (the propositional proofs don't actually use it — Q5 option 3 eliminates the axiom dependency for \`equiv\`-related theorems). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents c6e5d95 + eacd7ce commit 6806b93

5 files changed

Lines changed: 215 additions & 170 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
name: CodeQL Security Analysis
3+
4+
on:
5+
push:
6+
branches: [main, master]
7+
pull_request:
8+
branches: [main, master]
9+
schedule:
10+
- cron: '0 6 * * 1'
11+
12+
# Estate guardrail: cancel superseded runs so re-pushes / rebased PR
13+
# updates do not pile up queued runs against the shared account-wide
14+
# Actions concurrency pool. Applied only to read-only check workflows
15+
# (no publish/mutation), so cancelling a superseded run is always safe.
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
permissions:
21+
contents: read
22+
23+
jobs:
24+
analyze:
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 60
27+
permissions:
28+
contents: read
29+
security-events: write
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
include:
34+
- language: javascript-typescript
35+
build-mode: none
36+
- language: cpp
37+
build-mode: none
38+
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e # v6.0.3
42+
43+
- name: Initialize CodeQL
44+
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v3
45+
with:
46+
languages: ${{ matrix.language }}
47+
build-mode: ${{ matrix.build-mode }}
48+
49+
- name: Perform CodeQL Analysis
50+
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v3
51+
with:
52+
category: "/language:${{ matrix.language }}"

.github/workflows/echidna-validation.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
# valence-shell changes are not blocked by an unrelated upstream breakage.
2929
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
3030
runs-on: ubuntu-latest
31+
timeout-minutes: 30
3132
steps:
3233
- name: Checkout code
3334
uses: actions/checkout@1cce3390c2bfda521930d01229c073c7ff920824 # v6.0.2
@@ -49,6 +50,17 @@ jobs:
4950
cargo build --release --bin echidna
5051
echo "/tmp/echidna/target/release" >> $GITHUB_PATH
5152
53+
- name: Install proof provers
54+
run: |
55+
set -euo pipefail
56+
sudo apt-get update
57+
sudo apt-get install -y --no-install-recommends \
58+
lean \
59+
coq \
60+
agda \
61+
isabelle \
62+
z3
63+
5264
- name: Verify ECHIDNA available
5365
run: echidna list-provers
5466

@@ -60,6 +72,7 @@ jobs:
6072
correspondence:
6173
name: Lean 4 ↔ Rust Correspondence
6274
runs-on: ubuntu-latest
75+
timeout-minutes: 30
6376
steps:
6477
- name: Checkout code
6578
uses: actions/checkout@1cce3390c2bfda521930d01229c073c7ff920824 # v6.0.2

PROOF-NEEDS.md

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,20 @@
2929
| Coq | (closed) `mkdir_two_dirs_reversible` | `filesystem_composition.v` | Closed via LIFO restate — only standard funext (#56 closed) |
3030
| Coq | (closed) `overwrite_pass_equalizes_storage` | `rmo_operations.v` | Closed via `Hgeom` strengthened with `block_overwritten` (#57 closed — zero axioms) |
3131
| Coq | (closed) `obliterate_not_injective` | `rmo_operations.v` | Closed via threaded strengthened `Hgeom` through `multi_pass_same_start_same_result` (#58 closed — only standard funext) |
32-
| Idris2 | 16 `?holes` across 4 files (zero `partial` annotations) | `proofs/idris2/src/Filesystem/*.idr` | Type-stated, body un-discharged; classification per issue #119 |
32+
| Idris2 | `axStringEqRefl` (primitive-eq axiom) | `proofs/idris2/src/Filesystem/Axioms.idr:42` | `believe_me`-backed; registered in `.machine_readable/IDRIS2_AXIOMS.a2ml`; CI-gated via `.github/scripts/check-idris2-believe-me.sh` (Q1-C pilot 2026-06-02 PM) |
33+
| Idris2 | `axBits8EqRefl` (primitive-eq axiom) | `proofs/idris2/src/Filesystem/Axioms.idr:55` | `believe_me`-backed; registered in `.machine_readable/IDRIS2_AXIOMS.a2ml`; CI-gated (same pilot) |
34+
| Idris2 | 14 `?holes` across 4 files (zero `partial` annotations) | `proofs/idris2/src/Filesystem/*.idr` | `equivReflProof` closed via Q1-C pilot; `equivTransProof` closed via Q5-option-3 propositional `Equiv` migration (2026-06-03); rest per #119 |
3335

34-
**Idris2 holes by file (verified by grep against `proofs/idris2/src/Filesystem/*.idr`, 2026-06-02 PM):**
36+
**Idris2 holes by file (verified by grep against `proofs/idris2/src/Filesystem/*.idr`, 2026-06-03):**
3537

3638
| File | Top-level proof holes | Sub-holes (clause `prf` args) |
3739
|---|---|---|
38-
| `Operations.idr` | 7 (`mkdirRmdirReversibleProof`, `rmdirMkdirReversibleProof`, `touchRmReversibleProof`, `rmTouchReversibleProof`, `writeFileReversibleProof`, `operationIndependenceProof`, `cnoWriteSameContentProof`) | 4 (`?rmdirPrfAfterMkdir`, `?mkdirPrfAfterRmdir`, `?rmPrfAfterTouch`, `?touchPrfAfterRm`) |
40+
| `Operations.idr` | 7 (`mkdirRmdirReversibleProof`, `rmdirMkdirReversibleProof`, `touchRmReversibleProof`, `rmTouchReversibleProof`, `writeFileReversibleProof`, `operationIndependenceProof`, `cnoWriteSameContentProof` — now propositional `Equiv` shape) | 4 (`?rmdirPrfAfterMkdir`, `?mkdirPrfAfterRmdir`, `?rmPrfAfterTouch`, `?touchPrfAfterRm`) |
3941
| `Composition.idr` | 4 (`sequenceReversibleProof`, `compositionReversibleProof`, `undoRedoIdentityProof`, `undoRedoCompositionProof`) | 0 |
40-
| `Model.idr` | 2 (`equivReflProof`, `equivTransProof`; `equivSymProof` is closed via `andCommutative`) | 0 |
41-
| `RMO.idr` | 2 (`overwriteIrreversibleProof`, `hardwareEraseIrreversibleProof`; `appendOnlyAuditLogProof` is closed via `Refl`; `auditTrailCompletenessProof` is closed via redesign + `elemMap` + `elemAppRightSelf` — see #131) | 0 |
42+
| `Model.idr` | 0 (`equivSym` closed via constructor-swap on the propositional form; `equivRefl` + `equivTrans` closed structurally via `Data.List.Quantifiers` Q5-option-3 migration) | 0 |
43+
| `RMO.idr` | 3 (`overwriteIrreversibleProof`, `hardwareEraseIrreversibleProof`, `auditTrailCompletenessProof`; `appendOnlyAuditLogProof` is closed via `Refl`) | 0 |
4244

43-
Drift from previous PROOF-NEEDS.md tally (22 holes) to current (15 holes) is mechanical: `equivSymProof` and `appendOnlyAuditLogProof` closed silently during the 2026-06-02 morning sweep (visible by grep but the inventory text was not updated), and `auditTrailCompletenessProof` closed via signature redesign on 2026-06-03 (#131). No structural changes to the live source beyond #131 — this paragraph reconciles the count.
45+
Drift from previous PROOF-NEEDS.md tally (22 holes) to current (16 holes) is mechanical: `equivSymProof` and `appendOnlyAuditLogProof` closed silently during the 2026-06-02 morning sweep (visible by grep but the inventory text was not updated). No body changes — this paragraph reconciles the count.
4446

4547
All `partial` markers in `proofs/idris2/src/Filesystem/*.idr` were cleared 2026-06-02 (PRs #108 + #109, closing #89). The total `partial` count is zero.
4648

@@ -61,7 +63,6 @@ All `partial` markers in `proofs/idris2/src/Filesystem/*.idr` were cleared 2026-
6163
| 2026-06-02 | Idris2 build oracle | `idris-verification.yml` workflow + Justfile recipes shipped (PR #106, closes #70) |
6264
| 2026-06-02 | Idris2 0.8.0 parse fixes | `AuditEntry.proof` keyword-clash rename (PR #112); `hardwareEraseIrreversible` multi-line signature fix (PR #113); `reverseConcat` closed via `Data.List.revAppend` (PR #115) |
6365
| 2026-06-02 PM | Coq admit triumvirate | `mkdir_two_dirs_reversible` restated to LIFO and closed (#56); `overwrite_pass_equalizes_storage` strengthened with `block_overwritten` constraint, closed with zero new axioms (#57); `obliterate_not_injective` threaded through the strengthened lemma + `multi_pass_same_start_same_result`, closed with only standard funext (#58). Coq layer now has **zero `Admitted` markers** (only the justified `Axiom is_empty_dir_dec` remains). |
64-
| 2026-06-03 | Idris2 RMO `auditTrailCompleteness` redesign | Signature redesigned away from the `entries = []`-refutable shape into a per-insertion claim threading `(log, entry, p, insertedPath : path entry = p)`; closed via `elemMap` (stdlib) + a local `elemAppRightSelf` induction + `sym insertedPath` rewrite (#131, mirrors the #60 / #61 / #119A precedent). Zero new axioms; zero `believe_me`. |
6566

6667
### What Needs Proving (current, prioritised by tractability × value)
6768

@@ -95,60 +96,75 @@ about `(q == p)` on opaque `Path` values inside `elem`, which Idris2
9596
`HardwareEraseProof -> (Unit -> Filesystem) -> Void` is refuted by
9697
any non-empty `recovery` (the function exists trivially). Correct
9798
shape needs the recovery to take the post-erase state as input.
98-
- ~~`auditTrailCompletenessProof` (`RMO.idr:270`)~~ **CLOSED via #131**:
99-
the previous shape `Elem p (map AuditEntry.path entries)` was refuted
100-
by `entries = []`. Redesigned to thread a single `entry`, an
101-
`insertedPath : AuditEntry.path entry = p` premise, and reason about
102-
`appendAuditEntry log entry = log ++ [entry]`. Closure via
103-
`elemMap` (stdlib) + `elemAppRightSelf` (local helper) + a
104-
`sym insertedPath` rewrite. Zero new axioms, zero `believe_me`.
105-
106-
The remaining two should be filed/handled as **`#119` sub-issues** with
107-
the non-theorem refutations, in line with the #60 / #61 / #131
108-
precedent.
99+
- `auditTrailCompletenessProof` (`RMO.idr:270`): conclusion
100+
`Elem p (map AuditEntry.path entries)` is refuted by `entries = []`.
101+
Correct shape needs an "entry was appended" precondition naming the
102+
insertion event in the log.
103+
104+
These three should be filed as **`#119` sub-issues** with the
105+
non-theorem refutations, in line with the #60 / #61 precedent.
109106

110107
**4 Operations.idr sub-holes** (`?rmdirPrfAfterMkdir`,
111108
`?mkdirPrfAfterRmdir`, `?rmPrfAfterTouch`, `?touchPrfAfterRm`) — same
112109
primitive-eq blocker as the Cat-B set above. Each requires showing
113110
that the post-operation precondition holds, which reduces to
114111
`(p == p) = True` on opaque `Path` — blocked.
115112

116-
#### Priority 2 — primitive-eq groundwork (foundational, owner-decision required)
117-
118-
Every remaining tractable hole reduces to a `(s == s) = True` step on
119-
opaque `String` or `Bits8` — Idris2 0.8.0 only reduces these on
120-
literals. `DecEq Path` does NOT help because it transitively depends
121-
on `DecEq String`, which itself depends on primitive `==`.
122-
123-
Three closure paths, each requiring owner sign-off:
124-
125-
1. **Add `String` / `Bits8` reflexivity axioms** — `axStringEqRefl :
126-
(s : String) -> (s == s) = True := believe_me Refl`, gated by CI
127-
allow-list (per the Cat-D `believe_me` pattern). Smallest change,
128-
but introduces `believe_me` into the proof system which prior
129-
sessions explicitly avoided.
130-
2. **Migrate `Path` to a structural representation** — replace
131-
`String`-component paths with `Nat`-encoded interned identifiers.
132-
`Nat == Nat` IS reducible on opaque values. Bigger migration but
133-
no `believe_me`.
134-
3. **Reformulate every blocked theorem to use `decEq`-style branches**
135-
rather than `==`-style booleans. Avoids touching the `Eq` instance
136-
but ripples through ~20 theorem statements.
137-
138-
Until one path is chosen, the following holes are **frozen**:
139-
140-
- `equivReflProof` (Model.idr:216)
141-
- `equivTransProof` (Model.idr:244)
113+
#### Priority 2 — TWO blockers (primitive-eq UNBLOCKED; `all`/`foldMap` still blocked)
114+
115+
Status as of the 2026-06-02 PM Q1-C pilot (PR #133):
116+
117+
**Blocker 1 (primitive-eq) — UNBLOCKED.** The pilot adds
118+
`axStringEqRefl` + `axBits8EqRefl` in `Filesystem.Axioms` with a CI
119+
allow-list guard. `equivReflProof` closed as proof-of-concept. The
120+
axioms are operationally true and gated; soundness audit trail
121+
preserved via the `IDRIS2_AXIOMS.a2ml` registry.
122+
123+
**Blocker 2 (`all`/`foldMap` reduction) — DISCOVERED.** Attempting to
124+
close `equivTrans` in the same session revealed a second, independent
125+
problem: **`all p (x :: rest)` does NOT reduce to `(p x && all p rest)`
126+
by `Refl` in Idris2 0.8.0.** The `all` definition elaborates through
127+
`foldMap @{All}` — even though `foldMap`'s default body is `foldr`-
128+
based, the elaborator produces a `foldl`-shaped term that neither
129+
`Refl` nor any straightforward rewrite can directly destructure into
130+
the textbook `&&`-chain. Empirical witness:
131+
`example : all p (x :: rs) = (p x && all p rs) ; example = Refl`
132+
fails to typecheck — the unifier reports
133+
`foldl ... (neutral <+> p x) rs` on one side, `p x && Delay (all p rs)`
134+
on the other.
135+
136+
Consequently, **`equivTrans`, `cnoWriteSameContent`, and the 7
137+
reversibility theorems** are still blocked. The primitive-eq axioms
138+
unblock the LEAF reflexivity step but cannot bridge the foldMap-shaped
139+
reduction wall.
140+
141+
Three closure paths for blocker 2 (owner-decision required):
142+
143+
1. **Replace `equiv` with a structural `myAll`-based definition** that
144+
reduces by `Refl`. Touches the `equiv` shape but contained to
145+
`Model.idr`. Probably 1 PR.
146+
2. **Prove `allCons : all p (x :: rs) = (p x && all p rs)` via a chain
147+
of `foldl` lemmas**`foldlAndAccTrue` + `foldlAndFalseStays` +
148+
careful with-clauses. Pure mathematics but ~50 lines of fiddly
149+
proof engineering against an opaque elaboration order.
150+
3. **Migrate `equiv` to a propositional `All`-based shape** (via
151+
`Data.List.Quantifiers.All`) — cleanest mathematically but ripples
152+
through every call-site of `equiv`.
153+
154+
Until one of these lands, the following holes remain frozen even with
155+
the axiom infrastructure live:
156+
157+
- `equivTransProof` (Model.idr:353)
142158
- `cnoWriteSameContentProof` (Operations.idr:254)
143159
- 4 `?XXXPrfAfter` sub-holes in Operations.idr
144160
- 7 reversibility theorems in Operations.idr (mkdirRmdir, rmdirMkdir,
145161
touchRm, rmTouch, writeFile, operationIndependence,
146162
cnoWriteSameContent)
147163

148164
Separately, `undoRedoIdentityProof` and `undoRedoCompositionProof`
149-
need a Cat-A redesign (missing `isReversible op = True` precondition;
150-
provably refuted for non-reversible `op`) before primitive-eq is even
151-
relevant.
165+
still need a Cat-A redesign (missing `isReversible op = True`
166+
precondition; provably refuted for non-reversible `op`) before either
167+
blocker is relevant.
152168

153169
#### Priority 3 — Tier-S foundational (research-level)
154170

0 commit comments

Comments
 (0)