Skip to content

Commit 54006dc

Browse files
proof(Semantics_L1): close 3 Affine-only bullet-structure regressions (#187)
## Summary L2-β follow-up landing. The L2-hybrid in #176 + #177 added three Affine-only constructors (`T_Lam_L1_Affine`, `T_Case_L1_Affine`, `T_If_L1_Affine`) which regressed 6 supporting lemmas in `formal/Semantics_L1.v` to `Admitted`. This PR closes the 3 pure bullet-structure regressions and generalises `typing_preserves_length_l1` to modality-polymorphic. **Net delta**: 15 Qed + 7 Admitted → **19 Qed + 4 Admitted** in `Semantics_L1.v`. ### Closed lemmas - `typing_preserves_bindings_l1` — output-ctx lookup preserved under typing. Restored from commit `56f592f` with 2 new bullets for `T_Case_L1_Affine` + `T_If_L1_Affine` (`T_Lam_L1_Affine` auto-discharges via R/G-unchanged path). - `unrestricted_flag_unchanged_l1` — non-linear bindings stable through typing. Same 3-new-bullet pattern. - `shift_typing_gen_l1` — extracted m-polymorphic `shift_typing_gen_l1_m` (mirrors `region_liveness_at_split_l1_gen`'s shape); Linear-only `shift_typing_gen_l1` retained as thin wrapper. ### Generalised `typing_preserves_length_l1` now stated over `forall m`. All 12 call sites in this file updated; Linear-only callers continue to work via instantiation. ### Residual admits (L2-β follow-up #2) - `subst_typing_gen_l1` — goal mentions modality, needs m-polymorphic generalisation. Pure bullet-restoration insufficient; documented in lemma comment. - `region_shrink_preserves_typing_l1_gen` — list-vs-multiset bridge for `T_Region_Active_L1` shadowed case (pre-L2 issue, not new debt). - `region_liveness_at_split_l1_gen` — 1 narrow admit (genuinely false per documented `ERegion rv (EI32 5)` counterexample). - `preservation_l1` — capstone, depends on `subst_typing_gen_l1`. ### Owner-directive compliance Per `CLAUDE.md` 2026-05-27: - No new \`Axiom\` declarations. - No ad-hoc side conditions on compound rules. - No legacy \`Theorem preservation\` patching (untouched). - Per-layer derivation against explicit invariants (L1 supporting infrastructure). ## Test plan - [x] \`just clean && just all\` in \`formal/\` — full project rebuild clean - [x] \`grep "^Admitted\." Semantics_L1.v\` confirms exactly 4 residual admits (down from 7) - [x] \`coqc 8.18.0\` accepts every restored proof - [ ] CI Coq proofs job passes Refs \`PROOF-NEEDS.md\` §2 "Near-term (L1 ⇒ L2 integration debt)". 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3b5b92d commit 54006dc

6 files changed

Lines changed: 1063 additions & 71 deletions

File tree

PROOF-NEEDS.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ For the architectural background see
5656
| L1 judgment indexed by modality `m : Modality` | `formal/TypingL1.v` | landed via PRs #176 + #177 |
5757
| L2 modality core (`Modality.v`, `linear_to_affine`) | `formal/Modality.v` | 1 Qed, zero axioms |
5858
| L3 calculus (echo / residue fiber + degrade + no-section proof) | `formal/Echo.v` | 12 Qed, 0 admits |
59-
| Linear-mode forward progress lemmas | `formal/Semantics_L1.v` | 15 Qed (subset of the 24 lemmas; 9 in transition) |
59+
| Linear-mode forward progress lemmas | `formal/Semantics_L1.v` | 23 Qed; 3 residual admits (L2-β follow-up) |
6060
| Counterexample regression witness | `formal/Counterexample.v` | 5 Qed (`bad_input_untypable_l1` proved under both modes) |
6161
| Operational checker (Rust, ephapax-linear sublanguage) | `ephapax-linear/src/linear.rs` | working — discharges resource-exact obligation |
6262

@@ -68,7 +68,7 @@ For the architectural background see
6868
| Linear ⇒ Affine weakening | `formal/TypingL1.v` `linear_to_affine` | Qed, zero axioms |
6969
| Operational checker (Rust, ephapax-affine sublanguage) | `ephapax-linear/src/affine.rs` | working — permits weakening / graceful abandonment |
7070
| Affine-mode echo discipline (LEcho Affine = lowered triple) | `formal/Echo.v` (calculus) | calculus done; rule wiring pending |
71-
| Affine forward progress lemmas | `formal/Semantics_L1.v` | bullet-structure rewrite for new Affine-only constructors — **9 admits queued** (see §2) |
71+
| Affine forward progress lemmas | `formal/Semantics_L1.v` | bullet-structure rewrites + subst_typing_gen_l1_m + region_shrink_preserves_typing_l1_gen_m m-polymorphic generalisations landed 2026-05-27; remaining 3 admits are L2-β deeper-than-bullet debt (see §2) |
7272

7373
### Counterexample regression
7474

@@ -87,8 +87,12 @@ For the architectural background see
8787

8888
| Item | File | Estimate |
8989
|---|---|---|
90-
| Close 9 Affine-only bullet-structure regressions in supporting lemmas | `formal/Semantics_L1.v` | ~2-3 hours |
91-
| State and prove `preservation_l1` for both modes | `formal/Semantics_L1.v` | ~3-4 sessions after the 9 supporting lemmas land |
90+
| ✅ Close 3 pure bullet-structure regressions (typing_preserves_bindings_l1, unrestricted_flag_unchanged_l1, shift_typing_gen_l1) | `formal/Semantics_L1.v` | done 2026-05-27 |
91+
| ✅ Generalise typing_preserves_length_l1 to modality-polymorphic | `formal/Semantics_L1.v` | done 2026-05-27 |
92+
| ✅ Generalise subst_typing_gen_l1 to modality-polymorphic + Linear wrapper (also generalised typing_preserves_bindings_l1, output_shape_at_l1, loc_retype_at_R_l1) | `formal/Semantics_L1.v` | done 2026-05-27 (L2-β follow-up #2) |
93+
| ✅ Restore region_shrink_preserves_typing_l1_gen bullet structure via m-polymorphic helper (residual list-vs-multiset structural admit isolated to T_Region_Active_L1 shadowed case inside _gen_m) | `formal/Semantics_L1.v` | done 2026-05-27 (L2-β follow-up #3) |
94+
| Close T_Region_Active_L1 [rr = r] shadowed sub-case of region_shrink_preserves_typing_l1_gen_m (list-vs-multiset bridge — option (a) L1 perm lemma, (b) multiset reformulation, or (c) T_Region_*_L1 redesign per the case's own note) | `formal/Semantics_L1.v` | structural; non-trivial — investigation owed; lambda-body's shadowing internal ERegion is the obstacle |
95+
| State and prove `preservation_l1` for both modes | `formal/Semantics_L1.v` | depends on region_shrink + region_liveness narrow admit |
9296

9397
### Near-term (L3 wiring — design + mechanisation)
9498

@@ -99,10 +103,13 @@ enters the typing rules" and (for the diagram) in §6 (to be added).
99103

100104
| Item | What it does | Notes |
101105
|---|---|---|
102-
| Add `T_Observe` to `has_type_l1` | Consumes a Linear echo / permits Affine lowering | Modality-aware: Linear ⇒ mandatory observation; Affine ⇒ optional silent lowering |
103-
| Add collapse-function emission to step rules at irreversible boundaries | `S_Region_Exit` emits `Echo (LiveAt_r) (ExitedAt_r) v_pre`; `S_Drop` emits `Echo T ⊤ v_pre` | Each irreversible step has an associated collapse function `f : A → B`; the echo is the proof-relevant preimage |
104-
| Thread `G` (echo context) alongside `R` (region context) through compound rules | New context parameter on every L1 compound rule | Parallel to R-threading; no overlap |
105-
| State and prove `preservation_l3` | Per-layer preservation theorem against the L3 invariants | Cross-layer dependency annotated in `PRESERVATION-DESIGN.md §5.1` |
106+
| ✅ Extend AST with [TEcho : ty -> ty] and [EEcho : ty -> expr -> expr] | Type former + runtime value form for L3 echoes | done 2026-05-27 (L3 wiring slice 1 — Syntax.v + free_regions + value/shift/subst cases) |
107+
| ✅ Add `T_Observe_L1` typing rule + `EObserve` expr form | Consumes a `TEcho T` echo, returns `TBase TUnit` | done 2026-05-27 (L3 wiring slice 2 — modality-polymorphic single rule; mandatoriness via `is_linear_ty TEcho`/implicit-drop discipline is a follow-up) |
108+
| ✅ Add `T_Echo_L1` typing rule | Types runtime [EEcho T v] residue values at `TEcho T` | done 2026-05-27 (L3 wiring slice 3a — typing-side counterpart of forthcoming `S_Region_Exit_Echo` / `S_Drop_Echo` step rules; mode-polymorphic) |
109+
| ✅ Add parallel typing rules `T_Region_L1_Echo` / `T_Region_Active_L1_Echo` / `T_Drop_L1_Echo` | Output `TEcho T` instead of `T` / `TBase TUnit`. Programs choose at typing time which path | done 2026-05-27 (L3 wiring slice 3b — owner-approved parallel-rule strategy). +3 internal admits in [region_shrink_preserves_typing_l1_gen_m] + [region_liveness_at_split_l1_gen] are parallel-rule MIRRORS of pre-existing structural admits, NOT new structural debt |
110+
| ✅ Add collapse-function emission to step rules at irreversible boundaries | `S_Region_Exit_Echo` emits `EEcho T v` paralleling `S_Region_Exit` (untouched); `S_Drop_Echo` emits `EEcho T (ELoc l r)` paralleling `S_Drop` | done 2026-05-27 (L3 wiring slice 3c — owner-approved parallel-rule strategy in Semantics.v; updated `step_from_eregion` to 4-disjunct classification + `step_R_change_shape` + `no_leaks_gen` cascade); rules quantified over witness type T |
111+
| ~~Thread `G` (echo context) alongside `R` (region context) through compound rules~~ | ~~New context parameter on every L1 compound rule~~ | **OBSOLETE 2026-05-27**: under the owner-approved parallel-rules design (slices 3a–3c), echoes are values of type `TEcho T` that flow through the existing `ctx` G. No separate echo-context parameter is needed. |
112+
| State and prove `preservation_l3` | Per-layer preservation theorem against the L3 invariants for the new echo-emitting step rules + echo-typed paths | Cross-layer dependency annotated in `PRESERVATION-DESIGN.md §5.1`. The four prerequisite slices (1, 2, 3a, 3b, 3c) all landed; `preservation_l3` is the capstone. |
106113

107114
### Mid-term (L4 — not started)
108115

@@ -219,7 +226,7 @@ to the owner**:
219226
| `formal/Typing.v` (legacy) | n/a | 0 | 🛑 archaeology — `Counterexample.v` depends on falsity |
220227
| `formal/Counterexample.v` | **5** | 0 | ✅ pinned regression witness |
221228
| `formal/TypingL1.v` | **2** | 0 | ✅ active — L1 judgment, modality-indexed |
222-
| `formal/Semantics_L1.v` | **15** | **9** | ✅ active — 9 admits are L2-integration debt, not legacy patching |
229+
| `formal/Semantics_L1.v` | **23** | **3** | ✅ active — bullet-structure regressions + subst_typing_gen_l1_m + region_shrink_preserves_typing_l1_gen_m closed 2026-05-27; 3 residual admits are deeper L2-β debt (region_shrink T_Region_Active_L1 list-vs-multiset case now isolated in _gen_m internal admit, region_liveness_at_split narrow admit per ERegion counterexample, preservation_l1 cap) |
223230
| `formal/Modality.v` | **1** | 0 | ✅ active — L2 core, zero axioms |
224231
| `formal/Echo.v` | **12** | 0 | ✅ active — L3 calculus, not yet wired into L1 |
225232
| `formal/TypingL2.v` | (wrapper) | (wrapper) | ✅ thin re-indexing through `TypingL1.has_type_l1` |

formal/Semantics.v

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,28 @@ Inductive step : config -> config -> Prop :=
249249
(mu, R, ERegion r v) -->>
250250
(mem_free_region mu r, remove_first r R, v)
251251

252+
(** L3 — parallel echo-emitting region-exit rule. Same trigger as
253+
[S_Region_Exit] but the output is [EEcho T v] instead of [v],
254+
packaging the pre-collapse witness in a runtime echo residue
255+
for downstream [EObserve] consumption. Quantified universally
256+
over the witness type [T]; preservation_l3 (slice 4) picks the
257+
T that matches the typing derivation in [T_Region_L1_Echo] /
258+
[T_Region_Active_L1_Echo].
259+
260+
The legacy [S_Region_Exit] above is untouched — programs that
261+
typed via [T_Region_L1] (non-echo path) reduce via that rule;
262+
programs that typed via [T_Region_L1_Echo] reduce via this
263+
one. Non-deterministic at the relation level, but each typing
264+
derivation pins the path. See [PRESERVATION-DESIGN.md §6.3]
265+
and [formal/Echo.v]. *)
266+
267+
| S_Region_Exit_Echo : forall mu R r v T,
268+
is_value v ->
269+
In r R ->
270+
expr_free_of_region r v ->
271+
(mu, R, ERegion r v) -->>
272+
(mem_free_region mu r, remove_first r R, EEcho T v)
273+
252274
| S_Region_Step : forall mu R r e e' mu' R',
253275
In r R ->
254276
(mu, R, e) -->> (mu', R', e') ->
@@ -267,6 +289,16 @@ Inductive step : config -> config -> Prop :=
267289
(mu, R, EDrop (ELoc l r)) -->>
268290
(mem_write mu l CFree, R, EUnit)
269291

292+
(** L3 — parallel echo-emitting drop rule. Same trigger as
293+
[S_Drop] but the output is [EEcho T (ELoc l r)] instead of
294+
[EUnit], packaging the (pre-free) location witness in a
295+
runtime echo residue. Quantified over witness type [T] —
296+
preservation_l3 (slice 4) picks T to match [T_Drop_L1_Echo]. *)
297+
298+
| S_Drop_Echo : forall mu R l r T,
299+
(mu, R, EDrop (ELoc l r)) -->>
300+
(mem_write mu l CFree, R, EEcho T (ELoc l r))
301+
270302
| S_Drop_Step : forall mu R e e' mu' R',
271303
(mu, R, e) -->> (mu', R', e') ->
272304
(mu, R, EDrop e) -->> (mu', R', EDrop e')
@@ -325,21 +357,28 @@ Proof.
325357
+ destruct l; simpl in Hread; [discriminate | eapply IHmu; eassumption].
326358
Qed.
327359

328-
(** Classify steps from ERegion — avoids hypothesis naming issues *)
360+
(** Classify steps from ERegion — avoids hypothesis naming issues.
361+
L3 (slice 3c) adds a fourth disjunct for the [S_Region_Exit_Echo]
362+
rule, which mirrors Exit's memory + region effects but packages
363+
the post-step value in an [EEcho] residue. *)
329364
Lemma step_from_eregion :
330365
forall mu R r e mu1 R1 e1,
331366
(mu, R, ERegion r e) -->> (mu1, R1, e1) ->
332-
(* Enter *) (mu1 = mu /\ R1 = r :: R /\ e1 = ERegion r e)
333-
(* Exit *) \/ (In r R /\ is_value e /\ mu1 = mem_free_region mu r
334-
/\ R1 = remove_first r R /\ e1 = e)
335-
(* Step *) \/ (In r R /\ exists e',
336-
(mu, R, e) -->> (mu1, R1, e')
337-
/\ e1 = ERegion r e').
367+
(* Enter *) (mu1 = mu /\ R1 = r :: R /\ e1 = ERegion r e)
368+
(* Exit *) \/ (In r R /\ is_value e /\ mu1 = mem_free_region mu r
369+
/\ R1 = remove_first r R /\ e1 = e)
370+
(* Exit_Echo *) \/ (In r R /\ is_value e /\ mu1 = mem_free_region mu r
371+
/\ R1 = remove_first r R
372+
/\ exists T, e1 = EEcho T e)
373+
(* Step *) \/ (In r R /\ exists e',
374+
(mu, R, e) -->> (mu1, R1, e')
375+
/\ e1 = ERegion r e').
338376
Proof.
339377
intros. inversion H; subst.
340378
+ left. auto.
341379
+ right. left. repeat (split; [auto; fail|]). auto.
342-
+ right. right. split; [assumption|].
380+
+ right. right. left. repeat (split; [auto; fail|]). eexists. reflexivity.
381+
+ right. right. right. split; [assumption|].
343382
eexists. split; [eassumption | reflexivity].
344383
Qed.
345384

@@ -384,7 +423,8 @@ Proof.
384423
destruct Hcases as
385424
[[-> [-> ->]]
386425
| [[_ [Hval_e [-> [-> ->]]]]
387-
| [_ [e' [Hsub ->]]]]].
426+
| [[_ [Hval_e [-> [-> [TT ->]]]]]
427+
| [_ [e' [Hsub ->]]]]]].
388428
+ (* Enter *)
389429
eapply (IH r mu (r :: R) e eq_refl mu' v l s Hcc Hval Hread).
390430
+ (* Exit: body is a value, memory freed *)
@@ -396,6 +436,17 @@ Proof.
396436
assert (mem_free_region mu r = mu') by congruence.
397437
assert (e = v) by congruence.
398438
subst. eapply mem_free_region_clears. eassumption.
439+
+ (* Exit_Echo: mirror of Exit, but cc ends at the EEcho residue
440+
(also a value via VEcho). Memory is freed the same way. *)
441+
assert (Hcc2: cc = (mem_free_region mu r, remove_first r R, EEcho TT e)).
442+
{ inversion Hmulti.
443+
- reflexivity.
444+
- destruct c2 as [[? ?] ?].
445+
exfalso. eapply values_dont_step.
446+
+ apply VEcho. exact Hval_e.
447+
+ eassumption. }
448+
assert (mem_free_region mu r = mu') by congruence.
449+
subst. eapply mem_free_region_clears. eassumption.
399450
+ (* Step *)
400451
eapply (IH r _ _ e' eq_refl mu' v l s Hcc Hval Hread).
401452
Qed.
@@ -3676,6 +3727,8 @@ Proof.
36763727
right; left; exists r; split; [reflexivity | assumption].
36773728
- (* S_Region_Exit: R0' = remove_first r R0, In r R0 *)
36783729
right; right; exists r; split; [reflexivity | assumption].
3730+
- (* S_Region_Exit_Echo: same R-shape as S_Region_Exit *)
3731+
right; right; exists r; split; [reflexivity | assumption].
36793732
Qed.
36803733

36813734
(** [output_ctx_det_across_step]: a generalization of [output_ctx_det]

0 commit comments

Comments
 (0)