Skip to content

Commit 87dcab4

Browse files
feat(L2): preservation_l2_via_l1 — Phase D slice 4 partial landing (#211)
## Summary State the full `preservation_l2` goal in a comment block + ship the provable-now `L2_lift_l1` case as `preservation_l2_via_l1`. The β-case for `T_App_L2_Eff` requires generalising `subst_typing_gen_l1_m` (non-linear `T1`) — escalated per CLAUDE.md owner directive rather than closed via ad-hoc admit. ## What lands - **`preservation_l2_via_l1`** — closes the `L2_lift_l1` case of `preservation_l2` by deferring to `Semantics_L1.preservation_l1`. Provable directly; **no new admits/axioms** in `TypingL2.v`. - **`preservation_l2_lift_case`** corollary — same theorem, flatter shape for callers that already destructured the lift. - **Comprehensive doc block** stating the full `preservation_l2` goal, the planned structural-induction proof, and the architectural blocker for the `T_App_L2_Eff` β-case. ## Why partial — the architectural blocker The β-case (`S_App_Fun` applied to `T_App_L2_Eff`) requires invoking `subst_typing_gen_l1_m` (`Semantics_L1.v:1358`) with `T1 =` lambda parameter type. That lemma carries an `is_linear_ty T1 = true` precondition. Effect-typed lambdas at `TFunEff T1 T2 R_in R_out` may have non-linear `T1` (`TUnit` / `TBool` / `TI32` / …), so the substitution lemma does not fire in general. Per CLAUDE.md owner directive 2026-05-27 §"DO escalate before patching when a side condition is needed", this is the escalation point — generalising `subst_typing_gen_l1_m` to non-linear `T1` is next-slice infrastructure work, **not** an ad-hoc side-condition on the L2 typing rule. ## Verification (coqc 8.18.0) - Clean rebuild across `formal/` tree. - `Print Assumptions preservation_l2_via_l1` → `Semantics_L1.preservation_l1` (the legacy admit, honest carry-forward). **Zero NEW axioms introduced.** - `Print Assumptions weaken_modality` → unchanged ("Closed under the global context"). - Admit count: `TypingL2.v` retains 0 \`Admitted.\` / 0 \`Axiom\` / 0 inner \`admit.\`; `Semantics_L1.v` + `Semantics.v` unchanged. ## Owner-directive compliance - ✅ `Semantics.v` untouched - ✅ `Typing.v` untouched - ✅ `Counterexample.v` untouched - ✅ Zero new \`Admitted\`/\`Axiom\` in `TypingL2.v` - ✅ T_App_L2_Eff β-case **not** closed via \`Admitted.\` (per session-6 memory note's explicit anti-pattern) ## Next-slice infrastructure required to close full preservation_l2 1. **Generalise `subst_typing_gen_l1_m`** to non-linear `T1` (or sibling lemma) — substitution at a non-linear binding position. 2. **Inversion principles for `has_type_l2`** on `EApp`. 3. **Inversion on `T_Lam_L1_*_Eff`** for body extraction. (1) is the load-bearing item; (2) and (3) compose mechanically once (1) lands. ## Refs - `formal/PRESERVATION-DESIGN.md` §5.1 (lines 468-474) — load-bearing design quote endorsing the L2 venue for effect-typed application elimination. - PR #209 — T_App_L2_Eff constructor (Phase D slice 3). - PR #210 — STATE.a2ml shift to "state and prove preservation_l2". ## Test plan - [x] \`coqc 8.18.0\` clean rebuild - [x] \`Print Assumptions preservation_l2_via_l1\` → only legacy \`preservation_l1\` admit - [x] Zero new \`Admitted.\` / \`Axiom.\` / \`admit.\` in TypingL2.v - [x] GPG-signed commit - [ ] CI green - [ ] Auto-merge SQUASH fires 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5763648 commit 87dcab4

1 file changed

Lines changed: 109 additions & 1 deletion

File tree

formal/TypingL2.v

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
[T_Lam_*_L2]. L2 Phase 2 + L3 Phase 3 jointly close the L1
7272
gap. *)
7373

74-
From Ephapax Require Import Syntax Typing TypingL1 Modality.
74+
From Ephapax Require Import Syntax Typing TypingL1 Modality Semantics Semantics_L1.
7575

7676
(** ===== The L2 judgment (skeleton) =====
7777
@@ -244,3 +244,111 @@ Lemma weaken_modality_le_strict_is_weaken_modality :
244244
forall {R G e T R' G'} (H : has_type_l2 Linear R G e T R' G'),
245245
weaken_modality_le Linear_le_Affine H = weaken_modality H.
246246
Proof. reflexivity. Qed.
247+
248+
(** ===== Preservation at L2 (Phase D slice 4 — partial landing) =====
249+
250+
The full statement we want:
251+
252+
[Theorem preservation_l2 :
253+
forall m mu R e mu' R' e',
254+
step (mu, R, e) (mu', R', e') ->
255+
forall G T R_final G',
256+
has_type_l2 m R G e T R_final G' ->
257+
has_type_l2 m R' G e' T R_final G'.]
258+
259+
Structural induction on the L2 derivation has two cases:
260+
261+
- [L2_lift_l1] case: defer to [Semantics_L1.preservation_l1].
262+
Honest carry-forward of slice 4b legacy debt — [preservation_l1]
263+
is itself [Admitted] (lambda-rigidity gap for legacy [TFun]
264+
lambdas, [Semantics_L1.v:1708-1713]). This case introduces no
265+
new admit; it transparently surfaces the existing L1 admit
266+
through [Print Assumptions].
267+
268+
- [T_App_L2_Eff] case: case-analysis on [step]:
269+
* [S_App_Step1] (e1 reduces): close via IH on e1's L2
270+
sub-derivation; reconstruct via [T_App_L2_Eff].
271+
* [S_App_Step2] (e2 reduces): close via IH on e2's L2
272+
sub-derivation; reconstruct via [T_App_L2_Eff].
273+
* [S_App_Fun] (β-reduction): the load-bearing case. After
274+
inverting [has_type_l2 m R G (ELam T1 ebody)
275+
(TFunEff T1 T2 R_in R_out) R1 G'] through [L2_lift_l1] and
276+
[T_Lam_L1_*_Eff], and after applying [value_R_G_preserving_l1]
277+
to v2 to force [R_in = R] (and [G'' = G]), the residual
278+
obligation is L1-substitution at index 0:
279+
280+
[has_type_l1 m R G (subst 0 v2 ebody) T2 R_out G]
281+
282+
from the body typing [has_type_l1 m R (T1, false)::G ebody
283+
T2 R_out (T1, true)::G] and the argument typing
284+
[has_type_l1 m R G v2 T1 R G].
285+
286+
This is exactly the signature of [subst_typing_gen_l1_m]
287+
([Semantics_L1.v:1358]) — EXCEPT that lemma carries an
288+
[is_linear_ty T1 = true] precondition that does not hold in
289+
general (the lambda parameter type [T1] can be [TUnit],
290+
[TBool], [TI32], etc. for which [is_linear_ty] returns
291+
[false]).
292+
293+
Per CLAUDE.md owner directive 2026-05-27 §"DO escalate
294+
before patching", the proper response is to surface this
295+
as a layer-design item, not to bolt on an ad-hoc fix.
296+
297+
===== What this slice ships =====
298+
299+
[preservation_l2_via_l1] — the L2_lift_l1 case in standalone
300+
form. Provable from [preservation_l1] without touching the
301+
T_App_L2_Eff case. This is the largest fragment of
302+
preservation_l2 that lands cleanly under the current
303+
substitution-lemma generality.
304+
305+
===== Next-slice infrastructure required =====
306+
307+
To close the full [preservation_l2] over [has_type_l2]:
308+
309+
1. **Generalize [subst_typing_gen_l1_m]** (or provide a sibling
310+
lemma) to handle non-linear [T1] — substitution for
311+
non-linear parameter types in the lambda-binding position.
312+
Sits in [Semantics_L1.v] but is NEW infrastructure, not
313+
legacy patching.
314+
315+
2. **Inversion principles for [has_type_l2] on [EApp]**: a
316+
lemma stating that any [has_type_l2] derivation of
317+
[EApp e1 e2] at type [T2] decomposes into the [T_App_L2_Eff]
318+
premises (or [L2_lift_l1] wrapping [T_App_L1] for the
319+
[TFun] path). Coq's [inversion] tactic should produce this
320+
directly; if it gets stuck on dependent-typing constraints,
321+
a manual inversion lemma helps.
322+
323+
3. **Inversion on [T_Lam_L1_*_Eff]** to extract the body
324+
typing at [R_in]. Mechanical via [inversion] on the L1
325+
derivation.
326+
327+
Once (1) lands, (2) and (3) compose into the full
328+
[preservation_l2] proof body. *)
329+
330+
Theorem preservation_l2_via_l1 :
331+
forall m mu R e mu' R' e',
332+
step (mu, R, e) (mu', R', e') ->
333+
forall G T R_final G',
334+
TypingL1.has_type_l1 m R G e T R_final G' ->
335+
has_type_l2 m R' G e' T R_final G'.
336+
Proof.
337+
intros m mu R e mu' R' e' Hstep G T R_final G' Ht.
338+
apply L2_lift_l1.
339+
eapply Semantics_L1.preservation_l1; eassumption.
340+
Qed.
341+
342+
(** Corollary: the L2_lift_l1 case of [preservation_l2] over the
343+
full [has_type_l2] judgment is direct from
344+
[preservation_l2_via_l1] by destructuring the lift. *)
345+
346+
Corollary preservation_l2_lift_case :
347+
forall m mu R e mu' R' e',
348+
step (mu, R, e) (mu', R', e') ->
349+
forall G T R_final G' (H1 : TypingL1.has_type_l1 m R G e T R_final G'),
350+
has_type_l2 m R' G e' T R_final G'.
351+
Proof.
352+
intros m mu R e mu' R' e' Hstep G T R_final G' H1.
353+
eapply preservation_l2_via_l1; eassumption.
354+
Qed.

0 commit comments

Comments
 (0)