Skip to content

Commit 6f7c44e

Browse files
hyperpolymathclaude
andcommitted
feat(formal): F-1 transformer preservation + grow K-1 with let/vars + state P-2/P-3/F-3/F-4
Continues the formal/ Wave-0 programme (docs/PROOF-NEEDS.adoc), in the order F-1 → grow-K-1 → state-siblings. All Coq/Rocq 8.18, all axiom-free (`Print Assumptions`: "Closed under the global context"); no Admitted. F-1 — F1_TransformerPreservation.v (mechanized, composes on K-1): Models a face surface `fexp` + sugar, the transform `elaborate : fexp -> sexp` (ADR-010's T_F), and the face's own semantics `feval`. Proves the transform preserves meaning (feval f = seval (elaborate f)), then chains K-1's k1_preservation_holds to get end-to-end codegen preservation, plus the cross-face same-cube corollary (faces landing on the same canonical AST emit identical code). The observational split is F-2 (SameCube.agda). K-1 grown — K1Let_CodegenPreservation.v (mechanized): Re-proves codegen preservation for a richer fragment adding de Bruijn variables, `let`, and an environment; the target machine gains a locals register with IBind/IUnbind balancing. (`if`/structured control is the next increment — needs a target control construct + termination measure.) Siblings stated — Siblings_Stated.v (statements only, nothing proven): P-2 (progress + preservation), P-3 (borrow soundness + the explicit "reject #554" obligation), F-3 (pragma locality + alias-table functionality), F-4 (error-vocabulary faithfulness as a class/referent simulation). Each is a `Definition ..._statement : Prop` over section Variables, discharged at End into closed universally-quantified props — the Coq analogue of solo-core's statement-only Idris2 skeleton. Track wiring: justfile `check` now compiles all four in dependency order (F-1 Requires K-1) and asserts no axioms; _CoqProject + README updated. .hypatia-ignore extends the Coq-not-V-lang carve-out to the three new .v files. PROOF-NEEDS.adoc: F-1 absent→partial, F-3/F-4 absent→stated, formal/ file list + Wave-0 row refreshed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPG9mEQXFyA3k7NWAzMNMr
1 parent ac6e574 commit 6f7c44e

8 files changed

Lines changed: 511 additions & 51 deletions

File tree

.hypatia-ignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,9 @@ cicd_rules/banned_language_file:tools/res-to-affine/test/fixtures/phase3c.res
4545
# an inert no-op under the scanner's `grep -qxF` whole-line match.
4646
cicd_rules/vlang_detected:formal/K1_CodegenPreservation.v
4747
cicd_rules/banned_language_file:formal/K1_CodegenPreservation.v
48+
cicd_rules/vlang_detected:formal/K1Let_CodegenPreservation.v
49+
cicd_rules/banned_language_file:formal/K1Let_CodegenPreservation.v
50+
cicd_rules/vlang_detected:formal/F1_TransformerPreservation.v
51+
cicd_rules/banned_language_file:formal/F1_TransformerPreservation.v
52+
cicd_rules/vlang_detected:formal/Siblings_Stated.v
53+
cicd_rules/banned_language_file:formal/Siblings_Stated.v

docs/PROOF-NEEDS.adoc

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ than the prose corpus suggests:
6868
`TypeSafety.v` is example lemmas about list length, not AffineScript. Not
6969
core-metatheory.
7070
* **`formal/`** — the directory #513 names as the mechanized-proof target now
71-
**exists** (Coq/Rocq 8.18). It holds the Wave-0 **K-1 seed**
72-
(`formal/K1_CodegenPreservation.v`) — a complete, axiom-free
73-
codegen-preservation proof for a minimal nat/bool fragment (see <<outstanding>>).
71+
**exists** (Coq/Rocq 8.18), axiom-free throughout: `K1_CodegenPreservation.v`
72+
(K-1, minimal fragment) + `K1Let_CodegenPreservation.v` (K-1 grown with
73+
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>>).
7476
* **Research tracks** (not core soundness): `docs/academic/tropical-session-types/`
7577
(Lean), `proposals/echo-types/EchoEncodingFaithfulness.agda`,
7678
`proposals/idaptik/migrated/**/*Boundary.agda` (echo-types loss-with-residue,
@@ -222,8 +224,8 @@ cover them.
222224
well-typed face-`F` programs `p`. This is the *front-end* analogue of the
223225
backend keystone K-1; together they give end-to-end "same cube". Needs the
224226
AffineScript AST + wasm semantics formalised.
225-
| XL | `absent`
226-
| new
227+
| XL | `partial`
228+
| mechanized fragment: `formal/F1_TransformerPreservation.v` (composes on K-1)
227229

228230
| F-2
229231
| **Same-cube cross-face agreement (observational).** Any two faces compiling
@@ -242,8 +244,8 @@ cover them.
242244
jaffa→Js, pseudo→Pseudocode, lucid→Lucid, cafe→Cafe, +brand names) is a
243245
*function* (no name maps to two faces); and dispatch `--face` > pragma >
244246
extension is confluent (same source ⇒ same face). Face analogue of P-7.
245-
| M | `absent`
246-
| new; `lib/face_pragma.ml`
247+
| M | `stated`
248+
| Coq statement: `formal/Siblings_Stated.v` (F3); `lib/face_pragma.ml`
247249

248250
| F-4
249251
| **Error-vocabulary faithfulness (simulation).** Each `Face.format_*_for_face`
@@ -252,8 +254,8 @@ cover them.
252254
is a total simulation of the canonical error algebra (exhaustiveness is
253255
OCaml-checked; *semantic* faithfulness is not), so a face can never make error
254256
*X* read as a different error *Y*.
255-
| M | `absent`
256-
| new; `lib/face.ml`
257+
| M | `stated`
258+
| Coq statement: `formal/Siblings_Stated.v` (F4); `lib/face.ml`
257259

258260
| F-5
259261
| **`render_ty` faithfulness / non-collision.** The per-face type renaming
@@ -317,10 +319,10 @@ F-1 (full transformer preservation) is non-trivial rather than a formality.
317319
| Wave | Items | Gates
318320

319321
| 0
320-
| *In progress.* DONE: `formal/` stood up (Coq/Rocq); **K-1 seed mechanized**
321-
(`K1_CodegenPreservation.v`, axiom-free). NEXT: *state* P-2, P-3 (rejecting
322-
#554), F-1, F-3, F-4 as signatures, mirroring solo-core; cross-link
323-
`CAPABILITY-MATRIX.adoc` rows (K-4).
322+
| *Substantially done.* `formal/` stood up (Coq); **K-1 mechanized** + **grown**
323+
(variables/`let`); **F-1 mechanized** (composes on K-1); **P-2/P-3/F-3/F-4
324+
stated** as Coq signatures. All axiom-free. NEXT: grow K-1 with `if`;
325+
discharge a stated sibling; cross-link `CAPABILITY-MATRIX.adoc` rows (K-4).
324326
| —
325327

326328
| 1
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
F1_TransformerPreservation.v
6+
════════════════════════════
7+
Wave-0 seed of obligation **F-1** from docs/PROOF-NEEDS.adoc: the *real*
8+
same-cube theorem — each face's surface→canonical transform `T_F` preserves
9+
the typed-WASM denotation. F-1 is the front-end twin of K-1; this file
10+
*composes on top of* K-1 (it `Require`s it) exactly as the inventory says.
11+
12+
A "face" is an alternate surface syntax (rattle/jaffa/pseudo/lucid/cafe/…)
13+
whose parser yields a surface AST `fexp`; `elaborate : fexp -> sexp` is the
14+
transform `T_F` into the canonical AST that K-1's `compile` consumes. We give
15+
the face its OWN big-step semantics `feval` (what a reader of that surface
16+
expects) and prove the transform preserves it — then chain K-1 to get
17+
end-to-end codegen preservation, and state the cross-face "same cube".
18+
19+
Like K-1, everything here is complete and axiom-free (no `Admitted`).
20+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
21+
22+
Check: coqc -Q . ASFormal F1_TransformerPreservation.v (after K-1)
23+
*)
24+
25+
Require Import List.
26+
Import ListNotations.
27+
Require Import ASFormal.K1_CodegenPreservation.
28+
29+
(* ═══════════ A face surface: canonical forms + parser sugar ═══════════════ *)
30+
31+
(* `FSum3`/`FAll3` are surface sugar a face parser may emit (n-ary fold spelt
32+
as one node) — they stand in for any derived surface form that elaborates
33+
to a tree of canonical operations. *)
34+
Inductive fexp : Type :=
35+
| FNat (n : nat)
36+
| FBool (b : bool)
37+
| FAdd (a b : fexp)
38+
| FAnd (a b : fexp)
39+
| FSum3 (a b c : fexp) (* sugar for a + (b + c) *)
40+
| FAll3 (a b c : fexp). (* sugar for a && (b && c) *)
41+
42+
(* The transform T_F : surface AST -> canonical AST (`sexp` from K-1). *)
43+
Fixpoint elaborate (f : fexp) : sexp :=
44+
match f with
45+
| FNat n => SNat n
46+
| FBool b => SBool b
47+
| FAdd a b => SAdd (elaborate a) (elaborate b)
48+
| FAnd a b => SAnd (elaborate a) (elaborate b)
49+
| FSum3 a b c => SAdd (elaborate a) (SAdd (elaborate b) (elaborate c))
50+
| FAll3 a b c => SAnd (elaborate a) (SAnd (elaborate b) (elaborate c))
51+
end.
52+
53+
(* The face's own big-step semantics — what a reader of this surface expects,
54+
defined directly (not via elaboration), so that agreement is a theorem. *)
55+
Fixpoint feval (f : fexp) : option sval :=
56+
match f with
57+
| FNat n => Some (VNat n)
58+
| FBool b => Some (VBool b)
59+
| FAdd a b =>
60+
match feval a, feval b with
61+
| Some (VNat x), Some (VNat y) => Some (VNat (x + y))
62+
| _, _ => None
63+
end
64+
| FAnd a b =>
65+
match feval a, feval b with
66+
| Some (VBool x), Some (VBool y) => Some (VBool (andb x y))
67+
| _, _ => None
68+
end
69+
| FSum3 a b c =>
70+
match feval a, feval b, feval c with
71+
| Some (VNat x), Some (VNat y), Some (VNat z) => Some (VNat (x + (y + z)))
72+
| _, _, _ => None
73+
end
74+
| FAll3 a b c =>
75+
match feval a, feval b, feval c with
76+
| Some (VBool x), Some (VBool y), Some (VBool z) => Some (VBool (andb x (andb y z)))
77+
| _, _, _ => None
78+
end
79+
end.
80+
81+
(* ════════════ F-1: the transform preserves meaning ═══════════════════════ *)
82+
83+
(* Transform semantics-preservation: the face's own semantics agrees, on the
84+
nose, with elaborating-then-evaluating canonically. *)
85+
Theorem f1_transform_preserves_eval : forall f, feval f = seval (elaborate f).
86+
Proof.
87+
induction f as [ n | b | a IHa b IHb | a IHa b IHb
88+
| a IHa b IHb c IHc | a IHa b IHb c IHc ]; simpl.
89+
- reflexivity.
90+
- reflexivity.
91+
- rewrite IHa, IHb; reflexivity.
92+
- rewrite IHa, IHb; reflexivity.
93+
- (* FSum3: flat 3-way match vs nested — destruct to align *)
94+
rewrite IHa, IHb, IHc;
95+
destruct (seval (elaborate a)) as [[x | ] | ];
96+
destruct (seval (elaborate b)) as [[y | ] | ];
97+
destruct (seval (elaborate c)) as [[z | ] | ]; reflexivity.
98+
- rewrite IHa, IHb, IHc;
99+
destruct (seval (elaborate a)) as [[ | x] | ];
100+
destruct (seval (elaborate b)) as [[ | y] | ];
101+
destruct (seval (elaborate c)) as [[ | z] | ]; reflexivity.
102+
Qed.
103+
104+
(* End-to-end (the F-1 "same cube" at the value level): compiling the
105+
elaborated face program agrees with the face's own semantics — obtained by
106+
chaining the transform lemma with K-1's backend theorem. *)
107+
Theorem f1_codegen_preservation : forall f v,
108+
feval f = Some v ->
109+
wexec (compile (elaborate f)) [] = Some [obs v].
110+
Proof.
111+
intros f v Hf.
112+
apply k1_preservation_holds.
113+
rewrite <- f1_transform_preserves_eval.
114+
exact Hf.
115+
Qed.
116+
117+
(* Cross-face same-cube: two faces whose transforms land on the same canonical
118+
AST emit byte-identical code (hence the same wasm). The observational
119+
version for the trailing-statement/tail split is mechanized separately in
120+
invariant-path/proofs/SameCube.agda (F-2). *)
121+
Corollary f1_same_cube : forall f1 f2,
122+
elaborate f1 = elaborate f2 ->
123+
compile (elaborate f1) = compile (elaborate f2).
124+
Proof.
125+
intros f1 f2 H; rewrite H; reflexivity.
126+
Qed.
127+
128+
Print Assumptions f1_transform_preserves_eval.
129+
Print Assumptions f1_codegen_preservation.

formal/K1Let_CodegenPreservation.v

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
K1Let_CodegenPreservation.v
6+
═══════════════════════════
7+
K-1, GROWN. Extends the minimal K1_CodegenPreservation fragment with the
8+
first real binder: de Bruijn **variables** and **`let`**, evaluated under an
9+
environment. The target machine gains a **locals** register alongside the
10+
operand stack; compilation balances `IBind`/`IUnbind` so locals scope
11+
correctly. The codegen-preservation theorem is re-proven for this richer
12+
fragment — still complete and axiom-free (no `Admitted`).
13+
14+
This is a step of the K-1 obligation toward the real AST (binders →
15+
environments). `if`/control-flow is the next increment (it needs structured
16+
target control + a termination measure, deliberately out of this step).
17+
18+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
19+
Check: coqc -Q . ASFormal K1Let_CodegenPreservation.v
20+
*)
21+
22+
Require Import List.
23+
Import ListNotations.
24+
25+
(* ════════════════════════ Source (env-based, with let) ═══════════════════ *)
26+
27+
Inductive sval : Type := VNat (n : nat) | VBool (b : bool).
28+
29+
Inductive sexp : Type :=
30+
| SNat (n : nat)
31+
| SBool (b : bool)
32+
| SVar (i : nat) (* de Bruijn index into the environment *)
33+
| SAdd (a b : sexp)
34+
| SAnd (a b : sexp)
35+
| SLet (e1 e2 : sexp). (* let x = e1 in e2 ; x is index 0 in e2 *)
36+
37+
Definition senv := list sval.
38+
39+
Fixpoint seval (env : senv) (e : sexp) : option sval :=
40+
match e with
41+
| SNat n => Some (VNat n)
42+
| SBool b => Some (VBool b)
43+
| SVar i => nth_error env i
44+
| SAdd a b =>
45+
match seval env a, seval env b with
46+
| Some (VNat x), Some (VNat y) => Some (VNat (x + y))
47+
| _, _ => None
48+
end
49+
| SAnd a b =>
50+
match seval env a, seval env b with
51+
| Some (VBool x), Some (VBool y) => Some (VBool (andb x y))
52+
| _, _ => None
53+
end
54+
| SLet e1 e2 =>
55+
match seval env e1 with
56+
| Some v => seval (v :: env) e2
57+
| None => None
58+
end
59+
end.
60+
61+
(* ═══════════════ Target: stack machine + a locals register ════════════════ *)
62+
63+
Inductive wval : Type := WNat (n : nat) | WBool (b : bool).
64+
65+
Definition obs (v : sval) : wval :=
66+
match v with VNat n => WNat n | VBool b => WBool b end.
67+
68+
Inductive instr : Type :=
69+
| IPushN (n : nat)
70+
| IPushB (b : bool)
71+
| IGet (i : nat) (* push locals[i] onto the stack *)
72+
| IAdd
73+
| IAnd
74+
| IBind (* pop stack top, push it onto locals (new index 0) *)
75+
| IUnbind. (* drop locals[0]; operand stack unchanged *)
76+
77+
Definition code := list instr.
78+
Definition locals := list wval.
79+
Definition stack := list wval.
80+
81+
Definition wstep (i : instr) (l : locals) (s : stack) : option (locals * stack) :=
82+
match i, l, s with
83+
| IPushN n, l, s => Some (l, WNat n :: s)
84+
| IPushB b, l, s => Some (l, WBool b :: s)
85+
| IGet k, l, s =>
86+
match nth_error l k with
87+
| Some w => Some (l, w :: s)
88+
| None => None
89+
end
90+
| IAdd, l, WNat b :: WNat a :: s' => Some (l, WNat (a + b) :: s')
91+
| IAnd, l, WBool b :: WBool a :: s' => Some (l, WBool (andb a b) :: s')
92+
| IBind, l, w :: s' => Some (w :: l, s')
93+
| IUnbind, _ :: l', s => Some (l', s)
94+
| _, _, _ => None
95+
end.
96+
97+
Fixpoint wexec (c : code) (l : locals) (s : stack) : option (locals * stack) :=
98+
match c with
99+
| [] => Some (l, s)
100+
| i :: rest =>
101+
match wstep i l s with
102+
| Some (l', s') => wexec rest l' s'
103+
| None => None
104+
end
105+
end.
106+
107+
Fixpoint compile (e : sexp) : code :=
108+
match e with
109+
| SNat n => [IPushN n]
110+
| SBool b => [IPushB b]
111+
| SVar i => [IGet i]
112+
| SAdd a b => compile a ++ compile b ++ [IAdd]
113+
| SAnd a b => compile a ++ compile b ++ [IAnd]
114+
| SLet e1 e2 => compile e1 ++ [IBind] ++ compile e2 ++ [IUnbind]
115+
end.
116+
117+
(* ═════════════════════════ preservation (no Admitted) ════════════════════ *)
118+
119+
(* Execution distributes over code concatenation — the workhorse that lets the
120+
`let` case step its `IBind`/`IUnbind` cleanly without continuation juggling. *)
121+
Lemma wexec_app : forall c1 c2 l s,
122+
wexec (c1 ++ c2) l s =
123+
match wexec c1 l s with
124+
| Some (l', s') => wexec c2 l' s'
125+
| None => None
126+
end.
127+
Proof.
128+
induction c1 as [| i c1 IH]; intros c2 l s; simpl.
129+
- reflexivity.
130+
- destruct (wstep i l s) as [[l' s'] |]; [apply IH | reflexivity].
131+
Qed.
132+
133+
(* Codegen preservation, generalized over the operand stack `s` and carrying
134+
the environment/locals correspondence `l = map obs env`. `IUnbind` restores
135+
locals, so the machine returns to the same `l` it started with. *)
136+
Lemma compile_correct : forall e env v l s,
137+
seval env e = Some v ->
138+
l = map obs env ->
139+
wexec (compile e) l s = Some (l, obs v :: s).
140+
Proof.
141+
induction e as [n | b | i | a IHa b IHb | a IHa b IHb | e1 IH1 e2 IH2];
142+
intros env v l s Hev Hl; simpl in Hev; subst l.
143+
- (* SNat *) inversion Hev; subst; reflexivity.
144+
- (* SBool *) inversion Hev; subst; reflexivity.
145+
- (* SVar *)
146+
apply (map_nth_error obs) in Hev; simpl; rewrite Hev; reflexivity.
147+
- (* SAdd *)
148+
destruct (seval env a) as [[x | xb] |] eqn:Ha; try discriminate;
149+
destruct (seval env b) as [[y | yb] |] eqn:Hb; try discriminate;
150+
inversion Hev; subst; clear Hev; simpl compile;
151+
rewrite wexec_app, (IHa env (VNat x) (map obs env) s Ha eq_refl); simpl;
152+
rewrite wexec_app, (IHb env (VNat y) (map obs env) (WNat x :: s) Hb eq_refl); simpl;
153+
reflexivity.
154+
- (* SAnd *)
155+
destruct (seval env a) as [[xn | x] |] eqn:Ha; try discriminate;
156+
destruct (seval env b) as [[yn | y] |] eqn:Hb; try discriminate;
157+
inversion Hev; subst; clear Hev; simpl compile;
158+
rewrite wexec_app, (IHa env (VBool x) (map obs env) s Ha eq_refl); simpl;
159+
rewrite wexec_app, (IHb env (VBool y) (map obs env) (WBool x :: s) Hb eq_refl); simpl;
160+
reflexivity.
161+
- (* SLet *)
162+
destruct (seval env e1) as [v1 |] eqn:H1; try discriminate; simpl in Hev;
163+
simpl compile;
164+
rewrite wexec_app, (IH1 env v1 (map obs env) s H1 eq_refl); simpl;
165+
rewrite wexec_app, (IH2 (v1 :: env) v (obs v1 :: map obs env) s Hev eq_refl); simpl;
166+
reflexivity.
167+
Qed.
168+
169+
(* For a closed program (empty env) the locals balance back to empty and the
170+
result is the single wasm value on the operand stack. *)
171+
Theorem k1let_preservation_holds : forall e v,
172+
seval [] e = Some v ->
173+
wexec (compile e) [] [] = Some ([], [obs v]).
174+
Proof.
175+
intros e v H.
176+
exact (compile_correct e [] v [] [] H eq_refl).
177+
Qed.
178+
179+
Print Assumptions k1let_preservation_holds.

0 commit comments

Comments
 (0)