Skip to content

Commit 769d6ff

Browse files
hyperpolymathhyperpolymathclaude
authored
feat(formal): mechanize the four Wave-0 siblings (P-2, P-3, F-3, F-4) (#626)
## What Turns the four **stated** Wave-0 siblings in `Siblings_Stated.v` into **mechanized proofs**. Each new file builds a concrete model, proves the property, and ends in a `*_discharged : Siblings_Stated.<name> … := <proof>` line that type-checks the concrete proof *against the stated obligation*. All Coq/Rocq 8.18, **axiom-free** (`Print Assumptions` → *"Closed under the global context"*), no `Admitted`. | File | Obligation | Proven | |---|---|---| | `P2_Progress.v` | **P-2** | progress + preservation for a simply-typed first-order calculus (nat/bool/`add`/`if`), small-step CBV, with canonical forms | | `P3_BorrowSound.v` | **P-3** | a one-resource borrow checker is sound **and rejects the #554 witness** `[OBorrow; OMove; OUseRef]` | | `F3_PragmaDecidable.v` | **F-3** | the alias table is a function; detection factors through the scanned region (independent of bytes past the pragma) | | `F4_ErrorFaithful.v` | **F-4** | the face renderer preserves an error's **class + referent**, changing only vocabulary | `P3_BorrowSound.v` matches `lib/borrow.ml`'s **post-#554-fix** behaviour (per the new `SOUNDNESS.adoc`): the sound checker is exactly the one that now rejects use-after-move through a callee-returned borrow. ## Scope (honest) These are small fragments — enough to **discharge the obligation and pin its meaning**, not the full language. Growing them is Wave 1: functions/binders/QTT for P-2, the real borrow graph + Polonius (#553) for P-3. ## Wiring & docs - `justfile check` + `_CoqProject` compile **all eight** files in dependency order and assert no axioms — **11 closure reports**. - `.hypatia-ignore` extends the Coq-`.v`-isn't-V-lang carve-out to the four new files. - `PROOF-NEEDS.adoc`: P-2/P-3/F-3/F-4 → `partial` (mechanized model); Wave-0 row marked **done**. `README.adoc` + the `Siblings_Stated` header point at the discharge files. ## Check ```sh just -f formal/justfile check ``` Verified locally: 8 files, 11 closure reports, **zero axioms**. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KPG9mEQXFyA3k7NWAzMNMr --- _Generated by [Claude Code](https://claude.ai/code/session_01KPG9mEQXFyA3k7NWAzMNMr)_ Co-authored-by: hyperpolymath <paraordinate@yahoo.co.uk> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f28f525 commit 769d6ff

10 files changed

Lines changed: 419 additions & 32 deletions

.hypatia-ignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,11 @@ cicd_rules/vlang_detected:formal/F1_TransformerPreservation.v
5151
cicd_rules/banned_language_file:formal/F1_TransformerPreservation.v
5252
cicd_rules/vlang_detected:formal/Siblings_Stated.v
5353
cicd_rules/banned_language_file:formal/Siblings_Stated.v
54+
cicd_rules/vlang_detected:formal/F3_PragmaDecidable.v
55+
cicd_rules/banned_language_file:formal/F3_PragmaDecidable.v
56+
cicd_rules/vlang_detected:formal/F4_ErrorFaithful.v
57+
cicd_rules/banned_language_file:formal/F4_ErrorFaithful.v
58+
cicd_rules/vlang_detected:formal/P3_BorrowSound.v
59+
cicd_rules/banned_language_file:formal/P3_BorrowSound.v
60+
cicd_rules/vlang_detected:formal/P2_Progress.v
61+
cicd_rules/banned_language_file:formal/P2_Progress.v

docs/PROOF-NEEDS.adoc

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ than the prose corpus suggests:
7171
**exists** (Coq/Rocq 8.18), axiom-free throughout: `K1_CodegenPreservation.v`
7272
(K-1, minimal fragment) + `K1Let_CodegenPreservation.v` (K-1 grown with
7373
variables/`let`/environment) + `F1_TransformerPreservation.v` (F-1, composing
74-
on K-1) are mechanized; `Siblings_Stated.v` states P-2/P-3/F-3/F-4 as
75-
parametric propositions (see <<outstanding>>, <<faces>>).
74+
on K-1) are mechanized; `Siblings_Stated.v` states P-2/P-3/F-3/F-4, now
75+
**discharged for concrete models** in `P2_Progress.v` / `P3_BorrowSound.v` /
76+
`F3_PragmaDecidable.v` / `F4_ErrorFaithful.v` (see <<outstanding>>, <<faces>>).
7677
* **Research tracks** (not core soundness): `docs/academic/tropical-session-types/`
7778
(Lean), `proposals/echo-types/EchoEncodingFaithfulness.agda`,
7879
`proposals/idaptik/migrated/**/*Boundary.agda` (echo-types loss-with-residue,
@@ -122,14 +123,18 @@ obligations. They are the "we might have missed" half of the brief.
122123
| **Solo progress + preservation.** Discharge the two holes in `Soundness.idr`:
123124
give `Step` its constructors and prove
124125
`progress`/`preservation`/`affinePreservation`.
125-
| XL | `stated`
126-
| #513 must-have 1; solo-core Track F1 wks 3–12
126+
| XL | `partial`
127+
| #513 must-have 1; solo-core (Idris2) `stated`; **small-step progress +
128+
preservation mechanized** for a first-order nat/bool/`add`/`if` fragment in
129+
`formal/P2_Progress.v`
127130

128131
| P-3
129132
| **Borrow-graph soundness.** A well-typed program never observes a moved/aliased
130133
value. Must *exclude* the #554 counterexample (callee-returned borrow).
131-
| XL | `prose`
132-
| #513 must-have 2; counterexample #554; #553
134+
| XL | `partial`
135+
| #513 must-have 2; **mechanized** for a one-resource model in
136+
`formal/P3_BorrowSound.v` (sound checker rejects the #554 witness); #554 fixed
137+
(SOUNDNESS.adoc), Polonius #553
133138

134139
| P-4
135140
| **QTT affine usage.** Quantities `{0,1,ω}` are respected: `1`-vars used exactly
@@ -252,8 +257,9 @@ cover them.
252257
jaffa→Js, pseudo→Pseudocode, lucid→Lucid, cafe→Cafe, +brand names) is a
253258
*function* (no name maps to two faces); and dispatch `--face` > pragma >
254259
extension is confluent (same source ⇒ same face). Face analogue of P-7.
255-
| M | `stated`
256-
| Coq statement: `formal/Siblings_Stated.v` (F3); `lib/face_pragma.ml`
260+
| M | `partial`
261+
| **mechanized** for a model: `formal/F3_PragmaDecidable.v` (discharges the
262+
Siblings_Stated F3 statements); `lib/face_pragma.ml`
257263

258264
| F-4
259265
| **Error-vocabulary faithfulness (simulation).** Each `Face.format_*_for_face`
@@ -262,8 +268,9 @@ cover them.
262268
is a total simulation of the canonical error algebra (exhaustiveness is
263269
OCaml-checked; *semantic* faithfulness is not), so a face can never make error
264270
*X* read as a different error *Y*.
265-
| M | `stated`
266-
| Coq statement: `formal/Siblings_Stated.v` (F4); `lib/face.ml`
271+
| M | `partial`
272+
| **mechanized** for a model: `formal/F4_ErrorFaithful.v` (discharges the
273+
Siblings_Stated F4 statement); `lib/face.ml`
267274

268275
| F-5
269276
| **`render_ty` faithfulness / non-collision.** The per-face type renaming
@@ -327,10 +334,13 @@ F-1 (full transformer preservation) is non-trivial rather than a formality.
327334
| Wave | Items | Gates
328335

329336
| 0
330-
| *Substantially done.* `formal/` stood up (Coq); **K-1 mechanized** + **grown**
331-
(variables/`let`); **F-1 mechanized** (composes on K-1); **P-2/P-3/F-3/F-4
332-
stated** as Coq signatures. All axiom-free. NEXT: grow K-1 with `if`;
333-
discharge a stated sibling; cross-link `CAPABILITY-MATRIX.adoc` rows (K-4).
337+
| **Done.** `formal/` stood up (Coq); **K-1 mechanized** + **grown**
338+
(variables/`let`); **F-1 mechanized** (composes on K-1); and **P-2/P-3/F-3/F-4
339+
mechanized** for concrete models (small-step progress+preservation; borrow
340+
soundness rejecting #554; pragma locality + alias functionality; error
341+
faithfulness). 11 closure reports, all axiom-free. NEXT (Wave 1): grow each
342+
model toward the real language — `if`/functions/QTT in P-2, the real borrow
343+
graph in P-3 — and cross-link `CAPABILITY-MATRIX.adoc` rows (K-4).
334344
| —
335345

336346
| 1

formal/F3_PragmaDecidable.v

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
F3_PragmaDecidable.v
6+
════════════════════
7+
Mechanizes obligation **F-3** (face pragma detection) for a concrete model,
8+
discharging both statements from Siblings_Stated.v — axiom-free, no Admitted.
9+
10+
Models `lib/face_pragma.ml`: the alias table `parse_face_name` and the fact
11+
that `detect_in_source` reads only a bounded leading *region* and stops at
12+
the first code token, so its result is independent of bytes past the pragma.
13+
14+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
15+
*)
16+
17+
Require Import ASFormal.Siblings_Stated.
18+
19+
(* The six established faces. *)
20+
Inductive face := FCanonical | FPython | FJs | FPseudo | FLucid | FCafe.
21+
22+
(* ── F3_alias_functional: the alias table is a function ─────────────────── *)
23+
24+
(* Recognised pragma tokens (a representative slice of parse_face_name's
25+
accepted names + brand aliases). *)
26+
Inductive pname :=
27+
| NCanonical | NPython | NRattle | NJs | NJaffa
28+
| NPseudo | NLucid | NCafe | NUnknown.
29+
30+
Definition resolve_name (n : pname) : option face :=
31+
match n with
32+
| NCanonical => Some FCanonical
33+
| NPython | NRattle => Some FPython (* python / rattle / rattlescript *)
34+
| NJs | NJaffa => Some FJs (* js / javascript / jaffa(script) *)
35+
| NPseudo => Some FPseudo
36+
| NLucid => Some FLucid
37+
| NCafe => Some FCafe
38+
| NUnknown => None
39+
end.
40+
41+
Theorem alias_functional :
42+
forall n f1 f2, resolve_name n = Some f1 -> resolve_name n = Some f2 -> f1 = f2.
43+
Proof. intros n f1 f2 H1 H2; rewrite H1 in H2; injection H2; auto. Qed.
44+
45+
(* ── F3_pragma_local: detection ignores bytes past the scanned region ───── *)
46+
47+
(* A source = the pragma in its scanned leading window + arbitrary trailing
48+
bytes. `pragma_region` keeps the window and discards the trailing bytes;
49+
`detect` consults only the window. So detection cannot depend on anything
50+
past the pragma — the precise content of F-3. *)
51+
Record src := mkSrc { window : option pname; trailing : nat }.
52+
53+
Definition pragma_region (s : src) : src := mkSrc (window s) 0.
54+
55+
Definition detect (s : src) : option face :=
56+
match window (pragma_region s) with
57+
| Some n => resolve_name n
58+
| None => None
59+
end.
60+
61+
Theorem pragma_local :
62+
forall s1 s2, pragma_region s1 = pragma_region s2 -> detect s1 = detect s2.
63+
Proof. intros s1 s2 H; unfold detect; rewrite H; reflexivity. Qed.
64+
65+
(* ── discharge the stated obligations ──────────────────────────────────── *)
66+
67+
Definition F3_alias_discharged
68+
: F3_alias_functional face pname resolve_name
69+
:= alias_functional.
70+
71+
Definition F3_local_discharged
72+
: F3_pragma_local src face detect pragma_region
73+
:= pragma_local.
74+
75+
Print Assumptions F3_alias_discharged.
76+
Print Assumptions F3_local_discharged.

formal/F4_ErrorFaithful.v

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
F4_ErrorFaithful.v
6+
══════════════════
7+
Mechanizes obligation **F-4** (error-vocabulary faithfulness) for a concrete
8+
model, discharging the statement from Siblings_Stated.v — axiom-free.
9+
10+
Models `lib/face.ml`'s `format_*_for_face`: a face changes only the *words*
11+
of an error message; it must preserve the canonical error's *class* (which
12+
error) and *referent* (the offending identifier/site). The model makes
13+
`render` carry class + referent through unchanged while choosing
14+
face-specific vocabulary, and proves the rendering preserves both — so a
15+
face can never make error X read as a different error Y.
16+
17+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
18+
*)
19+
20+
Require Import ASFormal.Siblings_Stated.
21+
22+
Inductive face := FCanonical | FPython | FJs | FPseudo | FLucid | FCafe.
23+
24+
(* A canonical error: which error (class) and what it is about (referent). *)
25+
Record canon_error := mkErr { ce_class : nat; ce_referent : nat }.
26+
27+
(* A rendered message: the preserved class + referent, plus face-chosen words. *)
28+
Record rendered := mkRendered { r_class : nat; r_referent : nat; r_vocab : nat }.
29+
30+
(* Face-specific vocabulary for a given error class (the part that legitimately
31+
varies between faces — e.g. "single-use" vs "linear" vs "one-shot"). *)
32+
Definition face_vocab (f : face) (cls : nat) : nat :=
33+
match f with
34+
| FCanonical => cls
35+
| FPython => cls + 1
36+
| FJs => cls + 2
37+
| FPseudo => cls + 3
38+
| FLucid => cls + 4
39+
| FCafe => cls + 5
40+
end.
41+
42+
(* The face renderer: vocabulary may change; class and referent are carried
43+
through verbatim. *)
44+
Definition render (f : face) (e : canon_error) : rendered :=
45+
mkRendered (ce_class e) (ce_referent e) (face_vocab f (ce_class e)).
46+
47+
Definition rendered_class (r : rendered) : nat := r_class r.
48+
Definition rendered_referent (r : rendered) : nat := r_referent r.
49+
50+
Theorem error_faithful :
51+
forall f e,
52+
rendered_class (render f e) = ce_class e /\
53+
rendered_referent (render f e) = ce_referent e.
54+
Proof. intros f e; split; reflexivity. Qed.
55+
56+
(* ── discharge the stated obligation ───────────────────────────────────── *)
57+
58+
Definition F4_faithful_discharged
59+
: F4_error_faithful canon_error face rendered render
60+
ce_class ce_referent rendered_class rendered_referent
61+
:= error_faithful.
62+
63+
Print Assumptions F4_faithful_discharged.

formal/P2_Progress.v

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
P2_Progress.v
6+
═════════════
7+
Mechanizes obligation **P-2** (progress + preservation) for a minimal typed
8+
calculus, discharging both statements from Siblings_Stated.v — axiom-free,
9+
no Admitted.
10+
11+
Calculus: types Nat | Bool; terms numbers, booleans, `add`, and `if` (a real
12+
elimination form, so progress is non-trivial — a `tif` on a non-boolean is
13+
ruled out only by typing). Small-step, call-by-value. This is the classic
14+
"arith + bool" type-soundness core.
15+
16+
Scope (honest): this is the *simply-typed, first-order* core of the Solo
17+
fragment. Functions/`let`/binders (substitution or environments), products,
18+
sums, and the QTT/affine quantities are the next increments — solo-core's
19+
Duet/Ensemble direction. (Codegen preservation WITH `let`/variables is
20+
already mechanized in K1Let_CodegenPreservation.v.)
21+
22+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
23+
*)
24+
25+
Require Import ASFormal.Siblings_Stated.
26+
27+
Inductive ty := TNat | TBool.
28+
29+
Inductive tm :=
30+
| tnum (n : nat)
31+
| tbool (b : bool)
32+
| tadd (a b : tm)
33+
| tif (c t e : tm).
34+
35+
Inductive value : tm -> Prop :=
36+
| v_num : forall n, value (tnum n)
37+
| v_bool : forall b, value (tbool b).
38+
39+
Inductive has_type : tm -> ty -> Prop :=
40+
| T_Num : forall n, has_type (tnum n) TNat
41+
| T_Bool : forall b, has_type (tbool b) TBool
42+
| T_Add : forall a b, has_type a TNat -> has_type b TNat -> has_type (tadd a b) TNat
43+
| T_If : forall c t e T,
44+
has_type c TBool -> has_type t T -> has_type e T -> has_type (tif c t e) T.
45+
46+
Inductive step : tm -> tm -> Prop :=
47+
| S_Add1 : forall a a' b, step a a' -> step (tadd a b) (tadd a' b)
48+
| S_Add2 : forall a b b', value a -> step b b' -> step (tadd a b) (tadd a b')
49+
| S_AddNum : forall m n, step (tadd (tnum m) (tnum n)) (tnum (m + n))
50+
| S_If : forall c c' t e, step c c' -> step (tif c t e) (tif c' t e)
51+
| S_IfTrue : forall t e, step (tif (tbool true) t e) t
52+
| S_IfFalse : forall t e, step (tif (tbool false) t e) e.
53+
54+
(* ── canonical forms ───────────────────────────────────────────────────── *)
55+
56+
Lemma canon_nat : forall v, value v -> has_type v TNat -> exists n, v = tnum n.
57+
Proof. intros v Hv HT; inversion Hv; subst; inversion HT; subst; eauto. Qed.
58+
59+
Lemma canon_bool : forall v, value v -> has_type v TBool -> exists b, v = tbool b.
60+
Proof. intros v Hv HT; inversion Hv; subst; inversion HT; subst; eauto. Qed.
61+
62+
(* ── progress ──────────────────────────────────────────────────────────── *)
63+
64+
Theorem progress : forall t T, has_type t T -> value t \/ (exists t', step t t').
65+
Proof.
66+
intros t T HT; induction HT.
67+
- left; constructor.
68+
- left; constructor.
69+
- (* tadd a b *)
70+
right; destruct IHHT1 as [Hva | [a' Ha]].
71+
+ destruct (canon_nat a Hva HT1) as [m ->].
72+
destruct IHHT2 as [Hvb | [b' Hb]].
73+
* destruct (canon_nat b Hvb HT2) as [n ->]; eauto using S_AddNum.
74+
* eauto using S_Add2, v_num.
75+
+ eauto using S_Add1.
76+
- (* tif c t e *)
77+
right; destruct IHHT1 as [Hvc | [c' Hc]].
78+
+ destruct (canon_bool c Hvc HT1) as [[] ->]; eauto using S_IfTrue, S_IfFalse.
79+
+ eauto using S_If.
80+
Qed.
81+
82+
(* ── preservation ──────────────────────────────────────────────────────── *)
83+
84+
Theorem preservation : forall t t' T, has_type t T -> step t t' -> has_type t' T.
85+
Proof.
86+
intros t t' T HT Hstep; revert T HT.
87+
induction Hstep; intros T HT; inversion HT; subst.
88+
- apply T_Add; auto.
89+
- apply T_Add; auto.
90+
- constructor.
91+
- apply T_If; auto.
92+
- assumption.
93+
- assumption.
94+
Qed.
95+
96+
(* ── discharge the stated obligations (ctx instantiated to unit) ────────── *)
97+
98+
Definition P2_progress_discharged
99+
: P2_progress tm ty unit tt (fun (_ : unit) (t : tm) (T : ty) => has_type t T)
100+
value step
101+
:= progress.
102+
103+
Definition P2_preservation_discharged
104+
: P2_preservation tm ty unit (fun (_ : unit) (t : tm) (T : ty) => has_type t T)
105+
step
106+
:= fun (_ : unit) t t' T HT Hs => preservation t t' T HT Hs.
107+
108+
Print Assumptions P2_progress_discharged.
109+
Print Assumptions P2_preservation_discharged.

0 commit comments

Comments
 (0)