|
| 1 | +<!-- SPDX-License-Identifier: PMPL-1.0-or-later --> |
| 2 | +<!-- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> --> |
| 3 | + |
| 4 | +# Phase D — L2 effect-typed TFun, redesign memo (2026-05-28) |
| 5 | + |
| 6 | +This memo refines the Phase D plan in |
| 7 | +[`PRESERVATION-DESIGN.md §5.1`](PRESERVATION-DESIGN.md) based on |
| 8 | +analytical findings from the 2026-05-28 implementation attempt. |
| 9 | +**Read this before continuing Phase D work; it supersedes earlier |
| 10 | +slice-by-slice framings that omit the substitution-lemma side |
| 11 | +condition introduced below.** |
| 12 | + |
| 13 | +## What landed |
| 14 | + |
| 15 | +Two PRs landed on `main` during the 2026-05-28 session as part of |
| 16 | +Phase D's scaffolding plus an independent reformulation: |
| 17 | + |
| 18 | +| PR | Title | What | |
| 19 | +|---|---|---| |
| 20 | +| #200 | `syntax(L2 Phase 2 / Phase D slice 1): add TFunEff effect-typed function constructor` | Adds `TFunEff T1 T2 R_in R_out` to `ty` in `Syntax.v`; extends `free_regions` in `Typing.v` for the new case. Zero proof changes. Legacy `TFun T1 T2` preserved per CLAUDE.md owner directive. | |
| 21 | +| #201 | `syntax: expr_strictly_free_of_region (closes blocker 5)` | Adds a strict variant of `expr_free_of_region` that recurses unconditionally into `ERegion r' body` (no shadow short-circuit). Migrates L1 region-shrinkage lemma preconditions to the strict variant. **Blocker 5 (predicate weakness from shadow short-circuit) — CLOSED.** | |
| 22 | + |
| 23 | +## What we tried for slice 2 — and what broke |
| 24 | + |
| 25 | +The 2026-05-28 slice 2 attempt added two new typing rules to |
| 26 | +`TypingL1.v`: |
| 27 | + |
| 28 | +```coq |
| 29 | +| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, |
| 30 | + R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> |
| 31 | + R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G |
| 32 | +
|
| 33 | +| T_Lam_L1_Affine_Eff : forall R G T1 T2 e u R_in R_out, |
| 34 | + R_in ; ctx_extend G T1 |=L1[Affine] e : T2 -| R_out ; (T1, u) :: G -> |
| 35 | + R ; G |=L1[Affine] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G |
| 36 | +``` |
| 37 | + |
| 38 | +Adding new constructors to `has_type_l1` forces every existing |
| 39 | +inductive lemma over `has_type_l1` to cover the new cases. The |
| 40 | +following lemmas were exercised: |
| 41 | + |
| 42 | +| Lemma | T_Lam_L1_*_Eff case | Outcome | |
| 43 | +|---|---|---| |
| 44 | +| `count_occ_le_l1_m` | R-preserving outer | (not hit in this attempt; would close trivially) | |
| 45 | +| `region_shrink_preserves_typing_l1_gen_m` | Body's R_in unaffected by outer shrink → use body unchanged | ✅ closed via `eapply T_Lam_L1_*_Eff. eassumption.` | |
| 46 | +| `shift_typing_gen_l1_m` | Body's context extends by T1; shift by (S k) | ✅ closed via same pattern as legacy T_Lam | |
| 47 | +| `subst_typing_gen_l1_m` | **Substituted value lives at outer R, body at R_in** | 🛑 **STRUCTURAL FAIL** | |
| 48 | + |
| 49 | +The substitution lemma's `T_Lam_L1_Linear_Eff` case decomposes as: |
| 50 | + |
| 51 | +* Hypothesis: `Hregv : In rv R` (substituted value's region is in the |
| 52 | + outer environment). |
| 53 | +* Goal: produce a body derivation at `R_in` with `rv` substituted in. |
| 54 | + The body's typing rules (T_Loc_L1, etc.) for occurrences of the |
| 55 | + substituted variable require `In rv R_in`. |
| 56 | +* Available: nothing that ties `R` to `R_in`. |
| 57 | + |
| 58 | +**`In rv R` does not imply `In rv R_in`** under the current rule |
| 59 | +shape. The body's `R_in` is a free parameter of the lambda type — the |
| 60 | +lambda value can declare any `R_in` it likes. There is no enforced |
| 61 | +connection between `R_in` and the regions of the bound variable's |
| 62 | +type at lambda formation. Substitution lemma is therefore not |
| 63 | +generalisable to the new constructors without an additional |
| 64 | +invariant. |
| 65 | + |
| 66 | +## The structural insight |
| 67 | + |
| 68 | +This is the *same* missing invariant we hit at: |
| 69 | + |
| 70 | +* `Semantics_L1.v:553/621` (Phase C — shadowed `T_Region_Active_L1`) |
| 71 | +* `Semantics_L1.v:1694` (`preservation_l1` lambda-rigidity) |
| 72 | +* Sub-sub-case (i) of Phase C's option (c) attempt |
| 73 | + (`feedback`: body-input-shrinkage is not a theorem in general) |
| 74 | + |
| 75 | +Each instance is the L1 vocabulary lacking a way to relate the |
| 76 | +**outer** region environment (where caller values live) to a |
| 77 | +**nested** region environment (where a lambda body, or a region's |
| 78 | +scoped body, operates). The L1 rule shapes treat outer and inner |
| 79 | +environments as independent, but **substitution and call sites |
| 80 | +require a connection**. |
| 81 | + |
| 82 | +## The redesign — side condition on `T_Lam_L1_*_Eff` |
| 83 | + |
| 84 | +Add an explicit side condition to `T_Lam_L1_*_Eff` requiring the |
| 85 | +bound variable's regions to be live in the body's input environment: |
| 86 | + |
| 87 | +```coq |
| 88 | +| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, |
| 89 | + (forall r, In r (Typing.free_regions T1) -> In r R_in) -> (* NEW *) |
| 90 | + R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> |
| 91 | + R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G |
| 92 | +
|
| 93 | +| T_Lam_L1_Affine_Eff : forall R G T1 T2 e u R_in R_out, |
| 94 | + (forall r, In r (Typing.free_regions T1) -> In r R_in) -> (* NEW *) |
| 95 | + R_in ; ctx_extend G T1 |=L1[Affine] e : T2 -| R_out ; (T1, u) :: G -> |
| 96 | + R ; G |=L1[Affine] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G |
| 97 | +``` |
| 98 | + |
| 99 | +**Interpretation**: any region appearing in the bound variable's |
| 100 | +type `T1` is guaranteed to be live in the body's input environment |
| 101 | +`R_in`. When substituting a value `v : T1` with `free_regions T1 ⊇ {rv}` |
| 102 | +into the body, `In rv R_in` follows from the side condition. |
| 103 | + |
| 104 | +For typical lambda types: |
| 105 | + |
| 106 | +* `T1 = TBase TUnit`: `free_regions T1 = []`. Side condition vacuous. |
| 107 | + No constraint on R_in. ✓ |
| 108 | +* `T1 = TString rv`: `free_regions T1 = [rv]`. Side condition forces |
| 109 | + `In rv R_in`. ✓ |
| 110 | +* `T1 = TPair (TString r1) (TString r2)`: forces both `r1` and `r2` |
| 111 | + in R_in. ✓ |
| 112 | +* `T1 = TFun T1' T2'`: forces all free regions of T1' and T2' into |
| 113 | + R_in. ✓ (Closures over substring-typed values carry their region |
| 114 | + requirements.) |
| 115 | + |
| 116 | +This side condition is **discharge-able at lambda formation time**: |
| 117 | +the caller forming the lambda must establish the constraint. In |
| 118 | +practice this is automatic — any program that types a lambda body |
| 119 | +referencing a region in T1 already has that region in scope. |
| 120 | + |
| 121 | +## Why this closes `subst_typing_gen_l1_m` |
| 122 | + |
| 123 | +In the T_Lam_L1_Linear_Eff case of the substitution lemma: |
| 124 | + |
| 125 | +* Hypothesis from rule: `H : forall r, In r (free_regions T1) -> In r R_in`. |
| 126 | +* The substituted value `vv` has `In rv R` (lemma's preexisting |
| 127 | + Hregv) AND `vv : T1` with `In rv (free_regions T1)` (since |
| 128 | + `linear_value_is_loc_l1` constrains rv to be T1's region). |
| 129 | +* Apply H: `In rv R_in`. ✓ |
| 130 | +* Body typing after substitution uses `In rv R_in` to discharge |
| 131 | + T_Loc_L1's `In r R` premise at the substituted variable's |
| 132 | + occurrences. |
| 133 | + |
| 134 | +The lemma case closes via the same pattern as legacy T_Lam_L1_Linear |
| 135 | +but threaded through R_in instead of outer R. |
| 136 | + |
| 137 | +## Slice plan (revised) |
| 138 | + |
| 139 | +| Slice | Scope | Status | |
| 140 | +|---|---|---| |
| 141 | +| 1 | TFunEff syntax (`Syntax.v` + `free_regions`) | ✅ MERGED PR #200 | |
| 142 | +| 2 (redesigned) | `T_Lam_L1_*_Eff` with side condition; cascade through 8 inductive lemmas in `Semantics_L1.v` | Pending implementation | |
| 143 | +| 3 | `T_App_L1_Eff` + `TFunEff_count_monotone` connection lemma | Pending | |
| 144 | +| 4 | `preservation_l1` lambda-rigidity closure (`Semantics_L1.v:1694`) | Pending | |
| 145 | +| 5 | Phase B Slice 1 (TEcho linearity wire) + Phase C (list-vs-multiset bridge) unlocks | Pending | |
| 146 | + |
| 147 | +The strict-predicate reformulation (PR #201) is independent of these |
| 148 | +slices and already merged. |
| 149 | + |
| 150 | +## Implementation notes for slice 2 |
| 151 | + |
| 152 | +1. The 2 rules: add right after `T_Lam_L1_Affine` at |
| 153 | + `TypingL1.v:183` (verified compile order works). |
| 154 | +2. The side condition is a `forall r, In r (free_regions T1) -> In r R_in` |
| 155 | + Prop — straightforward to discharge in induction cases. |
| 156 | +3. **Cascade to 8 inductive Qed lemmas** in `Semantics_L1.v`: |
| 157 | + - `count_occ_le_l1_m` — trivial (R-preserving outer) |
| 158 | + - `region_shrink_preserves_typing_l1_gen_m` — re-apply rule with |
| 159 | + body unchanged + side condition unchanged (region shrink at |
| 160 | + outer doesn't touch the body's premise) |
| 161 | + - `typing_preserves_length_l1` — trivial |
| 162 | + - `typing_preserves_bindings_l1` — mirrors legacy T_Lam case |
| 163 | + - `unrestricted_flag_unchanged_l1` — similar |
| 164 | + - `shift_typing_gen_l1_m` — mirrors legacy T_Lam case |
| 165 | + - `value_R_G_preserving_l1` — lambda is a value; R-preserving |
| 166 | + case trivial |
| 167 | + - `subst_typing_gen_l1_m` — **uses the side condition** to discharge |
| 168 | + `In rv R_in` from `In rv (free_regions T1)` |
| 169 | +4. `Counterexample.v` is unaffected (no TFunEff or T_Lam_L1_*_Eff |
| 170 | + usage). |
| 171 | + |
| 172 | +## Owner directive compliance |
| 173 | + |
| 174 | +Per CLAUDE.md owner directive 2026-05-27: |
| 175 | + |
| 176 | +* ✅ Zero new `Admitted.` or `Axiom.` declarations |
| 177 | +* ✅ No patching of `Semantics.v` `preservation` (provably false) |
| 178 | +* ✅ No patching of legacy `Typing.v` judgment |
| 179 | +* ✅ Counterexample.v regression theorem untouched |
| 180 | +* ✅ All commits GPG-signed |
| 181 | +* ✅ Auto-merge ON for every PR |
| 182 | + |
| 183 | +## Anti-patterns this redesign avoids |
| 184 | + |
| 185 | +* **Strengthening `subst_typing_gen_l1_m`'s preconditions** with a |
| 186 | + per-call-site `In rv R_in` hypothesis would cascade to every |
| 187 | + caller and force them to prove the connection ad-hoc. The side |
| 188 | + condition at rule formation localises the constraint. |
| 189 | +* **Separate `has_type_l1_eff` judgment** would double the lemma |
| 190 | + surface and require bridge lemmas. Avoided by extending |
| 191 | + `has_type_l1` in place with a clean side condition. |
| 192 | +* **Constraining R_in = outer R** would defeat the purpose of |
| 193 | + effect-typing. Avoided. |
| 194 | + |
| 195 | +## Cross-references |
| 196 | + |
| 197 | +* [`PRESERVATION-DESIGN.md §5.1`](PRESERVATION-DESIGN.md) — original |
| 198 | + Phase D outline. |
| 199 | +* [`PROOF-NEEDS.md`](../PROOF-NEEDS.md) — proof debt audit (Phase B |
| 200 | + + Phase C deferral rows reference this Phase D closure). |
| 201 | +* `Semantics_L1.v:553/621` — Phase C structural admits that close |
| 202 | + once slice 4+5 land. |
| 203 | +* `Semantics_L1.v:1694` — `preservation_l1` lambda-rigidity admit |
| 204 | + that closes at slice 4. |
| 205 | + |
| 206 | +## Future re-attempts of perm_l1 (agent finding 2026-05-28) |
| 207 | + |
| 208 | +The standalone `region_env_perm_typing_l1` (existential output) |
| 209 | +agent attempt 2026-05-28 found 7 admits across two failure families: |
| 210 | + |
| 211 | +1. R-stability rules (`T_Lam_L1_*`, `T_Borrow_Val_L1`, `T_Echo_L1`) |
| 212 | + — force output_R = input_R as lists; IH gives perm-equiv not |
| 213 | + list-equal. |
| 214 | +2. Branch-agreement rules (`T_Case_L1_*`, `T_If_L1_*`) — branches |
| 215 | + yield outputs perm-equiv to a join but not list-equal. |
| 216 | + |
| 217 | +Both families dissolve once the L2 effect-typed structure is in |
| 218 | +place: R-stability rules get effect-typed wrappers that don't force |
| 219 | +list equality, and branch-agreement gets a per-branch effect that |
| 220 | +the join sums. |
| 221 | + |
| 222 | +`perm_l1` is therefore unblocked at slice 5 (post-T_App_L1_Eff |
| 223 | +landing), not at slice 2. |
0 commit comments