Skip to content

Commit 3c02b06

Browse files
docs(lessons): publish proof-engineering lessons from standards#134 session (Refs standards#134) (#105)
## Summary A single day's work under standards#124 (estate proof-debt epic) surfaced four recurring failure modes in proof-bearing code. This PR publishes them in both **human-readable** (asciidoc session report) and **machine-readable** (.a2ml) form so future humans and AI agents won't have to re-learn them. ## The four lessons | ID | Title | Bit us where | |----|-------|--------------| | `build-is-the-only-oracle` | In-file proof-status comments are NOT authoritative | `formal/Semantics.v` L3328 'FULLY CLOSED' lie → PR#87 propagated into ROADMAP+PROOF-NEEDS; `.machine_readable/6a2/STATE.a2ml` claimed '67 Qed, 0 Admitted' — also false | | `vacuous-wrapper-theorems` | A theorem can be vacuous — check the body, not the header | `src/formal/Ephapax/Formal/RegionLinear.idr::regionSafetyExtract` body is `= ne`; `noGCExtract` body is `= (ne, lc)`. ROADMAP cited them as "complete" — overstated | | `coq-show-existentials-recipe` | `Show. Show Existentials.` is the precise diagnostic | `formal/Semantics.v::preservation` had 910 open goals when in-file comment claimed "only ONE remains" | | `induction-doesnt-substitute-outer-slots` | Standard `remember (cfg) as cfg eqn:Hcfg` fix for preservation proofs | 910 → 29 (97%) via the right pattern after the anti-pattern `remember e_typed` failed | ## What's added - **`docs/sessions/SESSION-2026-05-20-proof-engineering-lessons.adoc`** (human-readable, 318 lines). Follows the existing `SESSION-*.adoc` convention in `docs/sessions/`. Each lesson has: the pattern (with code), where it bit us (with file:line citations), the recipe (do / don't), and cross-references to the PRs that fixed it. - **`.machine_readable/agent_instructions/lessons.a2ml`** (machine-readable, 196 lines). Read at session start by any AI agent touching formal-verification code. Each lesson is a `[[lesson]]` block with: `id` (kebab-case slug), `title`, `severity`, `applies-to` language list, `pattern`, `bit-us-where`, `do`, `dont`, `fixed-by-pr`, and cross-references to relevant memory files via `related-memory`. ## Cross-references - `formal/PRESERVATION-HANDOFF.md` (PR#102) — per-case checklist using the lesson-4 fix. - `RUST-SPARK-STANCE.adoc` § Honest gaps (PR#95) — lesson-2 examples flagged at seam level. - `.machine_readable/agent_instructions/methodology.a2ml` — the existing agent-methodology file; `lessons.a2ml` is its failure-catalogue sibling. ## Refs Refs standards#134 (**NOT Closes** — joint-close on agreement). ## Test plan - [x] No source files touched — pure documentation - [x] Asciidoc syntax matches existing `SESSION-*.adoc` files - [x] `.a2ml` syntax matches existing `agent_instructions/*.a2ml` files - [ ] CI green (governance + workflow security linter; no Rust/Coq jobs apply) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent ee56e89 commit 3c02b06

2 files changed

Lines changed: 514 additions & 0 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# lessons.a2ml — proof-engineering lessons surfaced through real failures.
5+
# Read at session start by any AI agent touching formal-verification code
6+
# (Coq, Idris2, Lean4, Agda). Each lesson is named, has a reproducer, and
7+
# tells the agent what to do (and what NOT to do) when it hits the pattern.
8+
#
9+
# Human-readable companion:
10+
# docs/sessions/SESSION-2026-05-20-proof-engineering-lessons.adoc
11+
#
12+
# Reference: ADR-002 in standards/agentic-a2ml/docs/
13+
14+
[metadata]
15+
version = "1.0.0"
16+
last-updated = "2026-05-20"
17+
surfaced-by = "standards#124 (estate proof-debt epic), single day's work 2026-05-20"
18+
surfaced-via-prs = ["proof-of-work#63", "ephapax#92", "ephapax#95", "ephapax#98", "ephapax#102", "ephapax#103"]
19+
20+
# ============================================================================
21+
# LESSON 1: in-file proof-status comments are NOT authoritative
22+
# ============================================================================
23+
24+
[[lesson]]
25+
id = "build-is-the-only-oracle"
26+
title = "In-file proof-status comments are NOT authoritative — build is the only oracle"
27+
severity = "high"
28+
applies-to = ["coq", "idris2", "lean4", "agda"]
29+
30+
pattern = """
31+
A source file carries a comment block claiming proof status:
32+
Qed.
33+
(* PROOF STATUS — FULLY CLOSED. Zero Admitted. ... *)
34+
The agent reads the comment, summarises it into downstream docs
35+
(ROADMAP, PROOF-NEEDS, STATE.a2ml) WITHOUT running the build.
36+
If the comment was ever wrong or silently drifted, the lie propagates.
37+
"""
38+
39+
bit-us-where = "ephapax/formal/Semantics.v L3328 claimed 'FULLY CLOSED (2026-04-27). Zero Admitted.' with Qed.; coqc 8.18.0 rejected it. ephapax#87 propagated the lie into ROADMAP + PROOF-NEEDS. ephapax/.machine_readable/6a2/STATE.a2ml claimed '67 Qed, 0 Admitted' — also false."
40+
41+
do = "Before citing or summarising any in-file proof-status claim, RUN THE BUILD:\n Coq: cd formal && coq_makefile -f _CoqProject -o Makefile.coq && make -f Makefile.coq\n Idris2: idris2 --build <pkg>.ipkg\n Lean4: lake build\nGreen build supports the claim. Red build invalidates the claim regardless of what the comment says."
42+
43+
dont = "Don't trust the comment. Don't propagate it into docs without running the build. Don't summarise 'Qed' as 'proven' without verification."
44+
45+
fixed-by-pr = ["ephapax#92", "ephapax#103"]
46+
47+
# ============================================================================
48+
# LESSON 2: "Theorem name in formal/" can be vacuous
49+
# ============================================================================
50+
51+
[[lesson]]
52+
id = "vacuous-wrapper-theorems"
53+
title = "A theorem can be vacuous — check the body, not the header docstring"
54+
severity = "high"
55+
applies-to = ["coq", "idris2", "lean4", "agda"]
56+
57+
pattern = """
58+
A formalisation file carries:
59+
||| Region Safety: a RegionBlock proves its result doesn't reference r.
60+
public export
61+
0 regionSafetyExtract :
62+
(r : RegionId) -> ... -> NoRegionInType r t -> ... -> NoRegionInType r t
63+
regionSafetyExtract r ctx t ne lc = ne
64+
Header claims something substantive. Body is = ne (return input
65+
unchanged). The theorem is sound (no escape hatches) but content-free.
66+
"""
67+
68+
bit-us-where = "ephapax/src/formal/Ephapax/Formal/RegionLinear.idr: regionSafetyExtract and noGCExtract both have tautological bodies. ROADMAP cited them as 'regionSafetyTheorem' / 'noGCTheorem' complete. Vacuous. Also seen earlier in vcl-ut (L2/L3/L5 predicates vacuously inhabited by AllParameterised)."
69+
70+
do = """
71+
When reviewing a 'Theorem complete' claim:
72+
1. Find the theorem in source.
73+
2. Look at the BODY, not the header docstring.
74+
3. If the body is `= <one of the inputs>`, the theorem is tautological — flag it.
75+
4. If the body is `= absurd <constructor>` over a no-inhabitant ctor, the theorem
76+
is narrow — check the header doesn't overstate.
77+
5. If the body is `Refl` with textually-identical LHS/RHS, same — vacuous.
78+
A real theorem chains supporting lemmas, does non-trivial case analysis,
79+
or constructs evidence.
80+
"""
81+
82+
dont = "Don't conclude 'theorem complete' from the type signature alone. Don't trust grep for theorem names — the name is a label, not a proof."
83+
84+
fixed-by-pr = ["ephapax#95"] # E3/E4 honestly OWED in the seam
85+
86+
# ============================================================================
87+
# LESSON 3: Show. + Show Existentials. for Coq diagnostics
88+
# ============================================================================
89+
90+
[[lesson]]
91+
id = "coq-show-existentials-recipe"
92+
title = "Show. Show Existentials. is the precise diagnostic for unfinished Coq proofs"
93+
severity = "medium"
94+
applies-to = ["coq"]
95+
96+
pattern = """
97+
A Coq proof script fails with:
98+
Error: (in proof X): Attempt to save an incomplete proof
99+
(there are remaining open goals).
100+
Natural assumption: 'one more tactic'. Often the reality: the script
101+
is closing zero goals, not one off from closure.
102+
"""
103+
104+
bit-us-where = "ephapax/formal/Semantics.v::preservation — coqc rejected Qed.; in-file comment claimed 'Only ONE case remains open'. Actual count via Show: 910 goals = 35 step rules × 26 typing rules. The try solve chain was closing ZERO."
105+
106+
do = """
107+
Before adding tactics blindly:
108+
end).
109+
Show. (* prints current goal *)
110+
Show Existentials. (* lists every unresolved metavariable + context *)
111+
Admitted.
112+
Then build. coqc prints the goal count at the top + every existential.
113+
Restore the Admitted. afterwards. Count tells you whether you need one
114+
more tactic or a structural rewrite (see lesson 'induction-doesnt-substitute').
115+
"""
116+
117+
dont = "Don't add 'try solve [...]' lines hoping for the best. Don't trust comment claims about which case is left open without running Show."
118+
119+
# ============================================================================
120+
# LESSON 4: induction doesn't substitute outer expression slots
121+
# ============================================================================
122+
123+
[[lesson]]
124+
id = "induction-doesnt-substitute-outer-slots"
125+
title = "induction Hstep doesn't substitute outer expression slots — remember (cfg) as cfg eqn:Hcfg is the standard fix"
126+
severity = "high"
127+
applies-to = ["coq"]
128+
129+
pattern = """
130+
A preservation-style theorem:
131+
Theorem preservation :
132+
forall mu R e mu' R' e',
133+
step (mu, R, e) (mu', R', e') ->
134+
forall G T G', has_type R G e T G' ->
135+
exists G_out, has_type R' G e' T G_out.
136+
Proof.
137+
intros mu R e mu' R' e' Hstep.
138+
induction Hstep; intros G0 T0 G0' Htype;
139+
inversion Htype; subst; ...
140+
141+
After `induction Hstep`, the outer `e` STAYS ABSTRACT. inversion Htype
142+
then produces ALL typing arms (m arms), not just the diagonal matching
143+
the step's expression. For n step rules × m typing rules you get n × m
144+
cross-cases. Trivial 'try solve [exfalso; discriminate]' fails because
145+
no discriminating equation is in scope.
146+
"""
147+
148+
bit-us-where = "ephapax/formal/Semantics.v::preservation — 35 step rules × 26 typing rules = 910 open goals. Existing try solve chain closed ZERO."
149+
150+
do = """
151+
Add `remember` BEFORE induction:
152+
intros mu R e mu' R' e' Hstep.
153+
remember (mu, R, e) as cfg eqn:Hcfg.
154+
remember (mu', R', e') as cfg' eqn:Hcfg'.
155+
induction Hstep; intros G0 T0 G0' Htype;
156+
inversion Hcfg; subst;
157+
inversion Hcfg'; subst;
158+
inversion Htype; subst;
159+
try solve [...].
160+
Now `Hcfg` after induction becomes (e.g. for S_StringNew):
161+
(mu0, R0, EStringNew r s) = (mu, R, e)
162+
inversion Hcfg; subst substitutes e := EStringNew r s everywhere
163+
including Htype. Then inversion Htype only generates the diagonal arm;
164+
the m-1 cross-arms are eliminated by constructor mismatch.
165+
ephapax#102 used this and reduced 910 → 29 (97%).
166+
"""
167+
168+
dont = """
169+
ANTI-PATTERN: `remember e_typed as e_orig eqn:He_orig` from `type of
170+
Htype`. This remembers the TYPING's expression (already abstract) — same
171+
shape as before. Solves nothing. Remember the CONFIG's expression slot
172+
(what induction Hstep substitutes for), not the typing's.
173+
174+
Also don't:
175+
- Replace induction with inversion — loses IHs for congruence cases.
176+
- Use dependent induction without thinking — can over-substitute and
177+
break the IH for congruence-rule cases too.
178+
"""
179+
180+
when-to-apply = "Any preservation-style proof where (a) the induction is on a relation with expression slots in its config, (b) the outer theorem quantifies over those slots, (c) typing-side inversion needs the expression form pinned."
181+
182+
fixed-by-pr = ["ephapax#102"]
183+
human-readable-doc = "docs/sessions/SESSION-2026-05-20-proof-engineering-lessons.adoc"
184+
185+
# ============================================================================
186+
# CROSS-CUTTING NOTES
187+
# ============================================================================
188+
189+
[notes]
190+
estate-pattern = "All four lessons cluster around the same root: claimed-but-unverified proof state. Lesson 1 = claims propagated without verification; Lesson 2 = claims look substantive but lack content; Lesson 3 = the precise way to verify a claim; Lesson 4 = the fix for a specific failure mode that triggers Lesson 1."
191+
192+
related-memory = [
193+
"feedback_foundation_first", # build-is-the-only-oracle rule
194+
"project_vclut_hole_deeper_than_documented", # vacuous predicates analog
195+
"project_proof_debt_rust_spark_audit_2026_05_18" # epic context
196+
]

0 commit comments

Comments
 (0)