diff --git a/ARCHITECTURE.adoc b/ARCHITECTURE.adoc new file mode 100644 index 00000000..1c0a7a69 --- /dev/null +++ b/ARCHITECTURE.adoc @@ -0,0 +1,48 @@ +== Architecture + +=== Overview + +This repository follows a modular, maintainable architecture designed +for clarity, scalability, and long-term sustainability. + +=== Directory Structure + +.... +. +├── src/ # Source code +├── tests/ # Test suites +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── config/ # Configuration files +├── LICENSE # License file +├── LICENSES/ # Full license texts +└── README.adoc # Project documentation +.... + +=== Design Principles + +* *Separation of Concerns*: Each module has a single responsibility +* *Testability*: Code is written to be easily testable +* *Documentation*: All public APIs are documented +* *Configuration*: Environment-specific settings are externalized + +=== Dependencies + +* External dependencies are minimized and clearly declared +* Version pinning is used for reproducibility + +=== Security Considerations + +* Sensitive data is never committed to the repository +* Secrets are managed through environment variables or secure vaults +* Regular dependency audits are performed + +=== Maintainability + +* Code follows consistent style guidelines +* Pull requests require review and CI checks +* Issues and discussions are tracked transparently + +''''' + +_Last updated: 2026-07-18_ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 607e3d8c..00000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,47 +0,0 @@ -# Architecture - -## Overview - -This repository follows a modular, maintainable architecture designed for clarity, scalability, and long-term sustainability. - -## Directory Structure - -``` -. -├── src/ # Source code -├── tests/ # Test suites -├── docs/ # Documentation -├── scripts/ # Utility scripts -├── config/ # Configuration files -├── LICENSE # License file -├── LICENSES/ # Full license texts -└── README.adoc # Project documentation -``` - -## Design Principles - -- **Separation of Concerns**: Each module has a single responsibility -- **Testability**: Code is written to be easily testable -- **Documentation**: All public APIs are documented -- **Configuration**: Environment-specific settings are externalized - -## Dependencies - -- External dependencies are minimized and clearly declared -- Version pinning is used for reproducibility - -## Security Considerations - -- Sensitive data is never committed to the repository -- Secrets are managed through environment variables or secure vaults -- Regular dependency audits are performed - -## Maintainability - -- Code follows consistent style guidelines -- Pull requests require review and CI checks -- Issues and discussions are tracked transparently - ---- - -*Last updated: 2026-07-18* diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc new file mode 100644 index 00000000..ce82d17a --- /dev/null +++ b/CHANGELOG.adoc @@ -0,0 +1,235 @@ +== Changelog + +All notable changes to Ephapax are documented here. + +=== https://github.com/hyperpolymath/ephapax/compare/v0.8.0...HEAD[Unreleased] + +==== Governance, CI & security-scan infrastructure (2026-06-24) + +* *Governance model formalised* (PR #314): added `+.github/CODEOWNERS+` +(review routing, sole-maintainer model), `+GOVERNANCE.adoc+`, and +`+MAINTAINERS.adoc+`. Salvaged from the closed estate-standardization +branch onto a clean base; prose headers normalised to `+CC-BY-SA-4.0+`. +* *`+standards+` reusable-workflow pins remediated* (PR #315): re-pinned +`+governance.yml+` / `+hypatia-scan.yml+` / `+scorecard.yml+` away from +`+5a93d9d57cc0+` — a ref that is *not* an ancestor of `+standards+` +HEAD, which broke `+governance / Check Workflow Staleness+` and made the +Hypatia reusable workflow reference a dead `+actions/cache+` SHA — to a +published HEAD. Subsequently bumped to the current `+d135b05…+` (PR +#316). +* *Hypatia neurosymbolic scan unblocked* — with the pin fixed, the +`+scan / Hypatia Neurosymbolic Analysis+` gate now runs to completion +instead of dying at "`Prepare all required actions`". It reports *37 +pre-existing findings* (5 critical, 7 high, 25 medium) — repo debt the +broken pin had been masking, *not* introduced here. Triage + fix is +*handed off* (see `+docs/governance/HYPATIA-HANDOFF.md+`); a visibility +patch for `+standards+`’ `+hypatia-scan-reusable.yml+` (publish findings +on failure rather than aborting opaquely under `+bash -e+`) is *drafted +but not yet applied*. +* *Stale PRs closed*: #310 (disjoint history; reintroduced fenced legacy +preservation work) and #311 (superseded — features already on `+main+`). + +==== Proof + stdlib wave (2026-06-01 → 2026-06-02) + +* *P43 — canonical-forms L1 modality-polymorphic* (PR #274): port +`+canonical_{unit,bool,i32,fun,prod,sum,string}_l1_m+` to the +modality-polymorphic L1 judgment. All 7 axiom-free (verified via +`+Print Assumptions+`). Prerequisite for `+progress_l1+` (P42). +* *P10 / P32 — Print Assumptions audit framework* (PR #270): per- module +whitelist guards mechanically certifying which axioms each +layer-keystone surfaces. `+tfuneff_lambda_retype_l1_m+` / +`+subst_typing_gen_l1_m_tfuneff+` etc. confirmed zero-axiom; the +expected residuals (`+preservation_l1+`, +`+region_liveness_at_split_l1_gen+`, +`+region_shrink_preserves_typing_l1_gen_m+`) listed. +* *P06 — `+step_pop_disjoint_from_type_l1+` partial proof* (PR #280): +stated + EASY cases Qed-closed (atomic non-region step rules; region +Enter/Exit/Exit_Echo; StringConcat; App_Step1; Fst/Snd; Borrow; Drop; +Copy). HARD cases (Let / LetLin / App_Step2 / If / Pair_Step / Inl / Inr +/ Case / Region_Step T_Region_L1) Admitted, tracked under issues #240 / +#241 / #242. +* *P28 — Rust↔Coq `+is_linear_ty+` bridge* (PR #273): kernel truth table +mechanically asserted. Pins the cross-language contract. +* *P59 — OwnershipKind from_byte/to_byte round-trip* (PR #277): +typed-wasm ADR-0002 carrier handshake locked in Coq. +* *D04 — Transactions as linear scopes* (PR #275): ACID semantics via +affine sublanguage. +* *D11 — Allen’s interval algebra* (PR #272): from DB-theory inventory. +* *D17 — exactly-once `+MessageHandle+` as linear typestate* (PR #279). +* *D18 — monoidal aggregates* (PR #281): +`+Sum+`/`+Product+`/`+Max+`/`+Min+`/ `+Count+`/`+And+`/`+Or+`/`+String+` +instances. +* *Truth restore + banned-preservation burial* (PR #263): doc-code +consistency for proof state. +* *Cluster D meander* (PR #278): L3/L4 status refresh + error-code +reconciliation + stale counts/paths. +* *CI — coq-build noble apt* (PR #282): switch from coqorg/coq:8.18 + +`+--user root+` quirks to ubuntu-latest + apt-coq, ~37s one-shot +(unblocks ~5 PRs). +* *CI — Track C panic-attack triage* (PR #271): classify findings per +#138. +* *Governance — R5b standards SHA bump* (PR #276): consume +version-string drift detection from standards#329. + +==== Phase 3b Stage 1a + 1b — L2 effect-typed TFun + L3 wiring conditional preservation (2026-05-30 → 2026-05-31) + +* *Stage 1a (PR #252, merged 2026-05-30 17:42Z)*: +`+tfuneff_lambda_free+` +** `+Counterexample_L2_nested.v+`. Admin-merged after local build oracle +GREEN with no axiom slippage (only the known-good +`+region_liveness_at_split_l1_gen+` axiom). +* *Stage 1b (PR #253)*: L1/L2 plumbing for the effect-typed `+TFun+` +track — `+expr_closed_below+` + closure helpers (Syntax.v), +body-transfer + closed- value G-poly helpers, +`+subst_typing_gen_l1_m_tfuneff+` Qed (zero axioms), and +`+preservation_l2+` β-case for closed `+TFunEff+` substituents. Builds +toward the unconditional `+preservation_l2+` track per +`+PRESERVATION-DESIGN.md §12.x+`. + +==== L3 wiring + L4 Phase A scaffold (2026-05-27 → 2026-05-28) + +* *L3 wiring (slice 4, 2026-05-27)*: +`+preservation_l3_region_active_echo+`, `+preservation_l3_drop_echo+`, +and the `+preservation_l3+` umbrella all Qed in +`+formal/Semantics_L1.v+` — each conditional on the +`+region_shrink_preserves_typing_l1_gen_m+` L1 structural admit. The +avoidable `+T_Region_L1_Echo+` mirror was closed in the same slice. Zero +new admits introduced. +* *L4 Phase A scaffold (2026-05-28)*: `+formal/L4.v+` lands with +`+ProgramMode+` (`+PModeLinear+` / `+PModeAffine+` / +`+PModeBoundaryMix+`) and `+program_mode_to_modality+` round-trip. +Definitions only by design — no theorems, no admits, no axioms. + +==== Four-layer preservation redesign (2026-05-26 → 2026-05-27) + +* *L1 — region capabilities* (PRs #153-line, integration branch +`+proof/l1-region-threading-design+`): introduces `+has_type_l1+` with +R-threading in `+formal/TypingL1.v+`. Supporting lemmas in +`+formal/Semantics_L1.v+`. Counterexample regression +(`+bad_input_untypable_l1+`) Qed in `+formal/Counterexample.v+`. +`+preservation_l1+` Admitted with 1 residual inner `+admit.+` covering +App / Pair / StringConcat (lambda- rigidity gap per +`+PRESERVATION-DESIGN.md §4.8+`). +* *L2 — Linear/Affine modality* (PR #176, this entry): `+has_type_l1+` +carries `+m : Modality+` parameter so a single judgment specialises to +ephapax-linear AND ephapax-affine. New `+formal/Modality.v+` with K-free +thin poset (`+modality_le+` at `+Type+` sort + +`+_refl+`/`+_trans+`/`+_prop+`). Mode-split constructors for `+T_Lam+` / +`+T_Case+` / `+T_If+`; remaining 21 rules modality-polymorphic. +*`+linear_to_affine+` Qed, closed under the global context (zero +axioms).* Mirrors `+EchoLinear.agda:38-58+`’s +`+weaken : LEcho linear → LEcho affine+`. `+weaken_modality+` at the L2 +layer dispatches through `+linear_to_affine+`. Six pre-L2 +`+Semantics_L1.v+` lemmas regressed to `+Admitted+` (bullet-structure +rewrite needed for the 3 new Affine-only constructors); restoration +tracked as L2-β. `+Counterexample.bad_input_untypable_l1+` generalised +to `+forall m, ~ has_type_l1 m ...+` — Qed under both indices. +* *L3 — Echo / residue* (PRs #166, #167-line, parallel track): +`+formal/Echo.v+` scaffold with +`+Mode + LEcho + decoration-commuting weakening+`; `+EchoR+` residue + +no-section irreversibility headline. Decouples residue layer from typing +layer; couples through L2’s thin-poset structure. +* *Disambiguation, durable*: `+ephapax-affine ≠ AffineScript+`. They are +separate languages sharing only the typed-wasm target. Per `+README+`, +`+CLAUDE.md+`, `+.machine_readable/disambiguation.a2ml+` hooks landed +via PRs #152 / affinescript#393 / typed-wasm#73. + +==== Proof state (2026-05-20 → 2026-05-21) + +* *Coq `+preservation+` reduction campaign*: 910 open goals → 12 (98.7% +reduction). PR chain: +** *#92* — honest framing: replaced unsubstantiated "`Qed 2026-04-27`" +claim with `+Admitted.+` +** *#102* — 910 → 29: standard preservation pattern +(`+remember (mu, R, e) as cfg+`) +** *#104* — quantitative status correction in docs +** *#106* — 29 → 22: universal-IH revert +(`+revert mu R e mu' R' e' Hcfg Hcfg'+` before `+induction Hstep+`) +** *#114* — region-invariance lemma `+step_R_eq_or_touches_region+` +(Qed-closed; infrastructure) +** *#115* — corrected Idris2 region-linearity theorem names in docs +** *#116* — 22 → 12: per-case manual closures of 10 β-reduction / +value-step constructors +** *#117* — canonicalised 5-phase closure plan in ROADMAP +§"`Preservation closure plan`" +** *#121* — Phase 1 scaffold: `+step_output_context_eq+` (Lemma B) +stated with induction skeleton +* *Idris2 `+%default partial → total+` campaign* (2026-05-20): merged 9 +PRs (#89, #90, #91, #93, #94, #96, #97, #99, #100) tightening totality +across the parser / typechecker / IR-codec layer. 80+ atomic functions +now provably total; zero `+assert_total+` / `+believe_me+` / +`+assert_smaller+` introduced; 14 retained `+covering+` markers for +documented Idris2 0.8.0 SCT limits. + +==== Documentation + +* *#113* — README + ROADMAP + EXPLAINME refactored for outside readers; +documentation map; quickstart-by-audience; proof status tables. +* Wiki refresh — `+Home.md+` rewritten as nav hub; new +`+Proof-status.md+`; 6 topic guides added (Linear-and-affine, +Region-calculus, Two-phase-compiler, What-can-go-wrong, +Comparison-to-other-languages, Glossary); `+_Sidebar.md+` for persistent +nav. +* Repo description + topics — added `+agda+`, `+idris2+`, +`+webassembly+`, `+formal-verification+`, `+affine-types+`, +`+region-calculus+`, `+compiler+`, `+operational-semantics+`, +`+programming-language+`. + +==== Added + +* *typed-wasm L7+L10 integration* (closes the typed-wasm-verify loop for +ephapax): +** *C6 (#70)* — `+ephapax-wasm+` emits an `+affinescript.ownership+` +custom section on every compile when any user fn has a Linear parameter. +Section format and encoder come from the new `+typed-wasm-verify+` Rust +crate (`+hyperpolymath/typed-wasm:crates/typed-wasm-verify/+`, rev +`+e11bb985+` at C6, bumped to `+67006edd+` at C7). Driven by +`+Ty::is_linear()+`; entries sorted by `+func_idx+` for deterministic +output. 4 new unit tests; `+cargo test --workspace+` clean. +** *C7 (#72)* — New `+--verify-ownership+` flag on `+ephapax compile+`. +After codegen and before output write, runs +`+typed_wasm_verify::verify_from_module+` on the emitted module and +surfaces any aliasing / linearity violation as a non-zero exit. Output: +`+✓ typed-wasm L7+L10 verification: clean+` on success; per-violation +diagnostics on failure. 3 new integration tests spawn the built binary. +** Net effect: ephapax-emitted wasm now has end-to-end L7+L10 guarantees +— producer side (custom section emission) + opt-in consumer side +(verifier flag), both backed by the canonical Rust verifier shared with +`+hyperpolymath/typed-wasm+`. +* `+module Qualified.Name+` declarations (dotted module paths) +* `+--+` Haskell-style line comments (alongside existing `+//+` and +`+/* */+`) +* Qualified names in imports: `+import Foo.Bar.Baz+` +* `+linear type+` and `+affine type+` modifiers on type definitions +* `+const_decl+`: module-level `+let NAME = expr+` bindings +* `+named_record_type_def+`: `+Type = Constructor { field: Type, ... }+` +syntax +* `+->+` return type syntax (alongside existing `+:+`) +* `+region name:+` colon syntax (alongside `+region name { }+`) +* `+Decl::Const+` variant in the AST for module-level constants + +==== Fixed + +* Keyword word-boundary fix: identifiers like `+init_result+` no longer +blocked by `+in+` keyword match + +==== Changed + +* Parser hardened: 80 production unwraps eliminated, proper ParseError +propagation +* License headers: 13 files migrated from EUPL-1.2 to PMPL-1.0-or-later +* Test unwraps replaced with `+.expect()+` for better diagnostics + +=== https://github.com/hyperpolymath/ephapax/compare/v0.7.0...v0.8.0[0.8.0] - 2026-03-16 + +* 80 examples across 8 categories +* Region-linear fusion type system +* `+__ffi()+` intrinsic for foreign function interface +* 15 compiler crates + +=== https://github.com/hyperpolymath/ephapax/releases/tag/v0.7.0[0.7.0] - 2026-02-15 + +* Initial public release +* Linear type system with region annotations +* Pattern matching, modules, error handling +* REPL and interpreter diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 4c82233b..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,188 +0,0 @@ - - - -# Changelog - -All notable changes to Ephapax are documented here. - -## [Unreleased] - -### Governance, CI & security-scan infrastructure (2026-06-24) - -- **Governance model formalised** (PR #314): added `.github/CODEOWNERS` - (review routing, sole-maintainer model), `GOVERNANCE.adoc`, and - `MAINTAINERS.adoc`. Salvaged from the closed estate-standardization - branch onto a clean base; prose headers normalised to `CC-BY-SA-4.0`. -- **`standards` reusable-workflow pins remediated** (PR #315): re-pinned - `governance.yml` / `hypatia-scan.yml` / `scorecard.yml` away from - `5a93d9d57cc0` — a ref that is **not** an ancestor of `standards` HEAD, - which broke `governance / Check Workflow Staleness` and made the Hypatia - reusable workflow reference a dead `actions/cache` SHA — to a published - HEAD. Subsequently bumped to the current `d135b05…` (PR #316). -- **Hypatia neurosymbolic scan unblocked** — with the pin fixed, the - `scan / Hypatia Neurosymbolic Analysis` gate now runs to completion - instead of dying at "Prepare all required actions". It reports **37 - pre-existing findings** (5 critical, 7 high, 25 medium) — repo debt the - broken pin had been masking, **not** introduced here. Triage + fix is - **handed off** (see `docs/governance/HYPATIA-HANDOFF.md`); a visibility - patch for `standards`' `hypatia-scan-reusable.yml` (publish findings on - failure rather than aborting opaquely under `bash -e`) is **drafted but - not yet applied**. -- **Stale PRs closed**: #310 (disjoint history; reintroduced fenced legacy - preservation work) and #311 (superseded — features already on `main`). - -### Proof + stdlib wave (2026-06-01 → 2026-06-02) - -- **P43 — canonical-forms L1 modality-polymorphic** (PR #274): port - `canonical_{unit,bool,i32,fun,prod,sum,string}_l1_m` to the - modality-polymorphic L1 judgment. All 7 axiom-free (verified via - `Print Assumptions`). Prerequisite for `progress_l1` (P42). -- **P10 / P32 — Print Assumptions audit framework** (PR #270): per- - module whitelist guards mechanically certifying which axioms each - layer-keystone surfaces. `tfuneff_lambda_retype_l1_m` / - `subst_typing_gen_l1_m_tfuneff` etc. confirmed zero-axiom; the - expected residuals (`preservation_l1`, `region_liveness_at_split_l1_gen`, - `region_shrink_preserves_typing_l1_gen_m`) listed. -- **P06 — `step_pop_disjoint_from_type_l1` partial proof** (PR #280): - stated + EASY cases Qed-closed (atomic non-region step rules; region - Enter/Exit/Exit_Echo; StringConcat; App_Step1; Fst/Snd; Borrow; - Drop; Copy). HARD cases (Let / LetLin / App_Step2 / If / Pair_Step - / Inl / Inr / Case / Region_Step T_Region_L1) Admitted, tracked - under issues #240 / #241 / #242. -- **P28 — Rust↔Coq `is_linear_ty` bridge** (PR #273): kernel truth - table mechanically asserted. Pins the cross-language contract. -- **P59 — OwnershipKind from_byte/to_byte round-trip** (PR #277): - typed-wasm ADR-0002 carrier handshake locked in Coq. -- **D04 — Transactions as linear scopes** (PR #275): ACID semantics - via affine sublanguage. -- **D11 — Allen's interval algebra** (PR #272): from DB-theory inventory. -- **D17 — exactly-once `MessageHandle` as linear typestate** (PR #279). -- **D18 — monoidal aggregates** (PR #281): `Sum`/`Product`/`Max`/`Min`/ - `Count`/`And`/`Or`/`String` instances. -- **Truth restore + banned-preservation burial** (PR #263): doc-code - consistency for proof state. -- **Cluster D meander** (PR #278): L3/L4 status refresh + error-code - reconciliation + stale counts/paths. -- **CI — coq-build noble apt** (PR #282): switch from - coqorg/coq:8.18 + `--user root` quirks to ubuntu-latest + apt-coq, - ~37s one-shot (unblocks ~5 PRs). -- **CI — Track C panic-attack triage** (PR #271): classify findings - per #138. -- **Governance — R5b standards SHA bump** (PR #276): consume - version-string drift detection from standards#329. - -### Phase 3b Stage 1a + 1b — L2 effect-typed TFun + L3 wiring conditional preservation (2026-05-30 → 2026-05-31) - -- **Stage 1a (PR #252, merged 2026-05-30 17:42Z)**: `tfuneff_lambda_free` - + `Counterexample_L2_nested.v`. Admin-merged after local build oracle - GREEN with no axiom slippage (only the known-good - `region_liveness_at_split_l1_gen` axiom). -- **Stage 1b (PR #253)**: L1/L2 plumbing for the effect-typed `TFun` track — - `expr_closed_below` + closure helpers (Syntax.v), body-transfer + closed- - value G-poly helpers, `subst_typing_gen_l1_m_tfuneff` Qed (zero axioms), - and `preservation_l2` β-case for closed `TFunEff` substituents. Builds - toward the unconditional `preservation_l2` track per - `PRESERVATION-DESIGN.md §12.x`. - -### L3 wiring + L4 Phase A scaffold (2026-05-27 → 2026-05-28) - -- **L3 wiring (slice 4, 2026-05-27)**: `preservation_l3_region_active_echo`, - `preservation_l3_drop_echo`, and the `preservation_l3` umbrella all Qed in - `formal/Semantics_L1.v` — each conditional on the - `region_shrink_preserves_typing_l1_gen_m` L1 structural admit. The - avoidable `T_Region_L1_Echo` mirror was closed in the same slice. Zero new - admits introduced. -- **L4 Phase A scaffold (2026-05-28)**: `formal/L4.v` lands with `ProgramMode` - (`PModeLinear` / `PModeAffine` / `PModeBoundaryMix`) and - `program_mode_to_modality` round-trip. Definitions only by design — no - theorems, no admits, no axioms. - -### Four-layer preservation redesign (2026-05-26 → 2026-05-27) - -- **L1 — region capabilities** (PRs #153-line, integration branch - `proof/l1-region-threading-design`): introduces `has_type_l1` with - R-threading in `formal/TypingL1.v`. Supporting lemmas in - `formal/Semantics_L1.v`. Counterexample regression (`bad_input_untypable_l1`) - Qed in `formal/Counterexample.v`. `preservation_l1` Admitted with 1 - residual inner `admit.` covering App / Pair / StringConcat (lambda- - rigidity gap per `PRESERVATION-DESIGN.md §4.8`). -- **L2 — Linear/Affine modality** (PR #176, this entry): `has_type_l1` - carries `m : Modality` parameter so a single judgment specialises to - ephapax-linear AND ephapax-affine. New `formal/Modality.v` with K-free - thin poset (`modality_le` at `Type` sort + `_refl`/`_trans`/`_prop`). - Mode-split constructors for `T_Lam` / `T_Case` / `T_If`; remaining 21 - rules modality-polymorphic. **`linear_to_affine` Qed, closed under the - global context (zero axioms).** Mirrors `EchoLinear.agda:38-58`'s - `weaken : LEcho linear → LEcho affine`. `weaken_modality` at the L2 - layer dispatches through `linear_to_affine`. Six pre-L2 `Semantics_L1.v` - lemmas regressed to `Admitted` (bullet-structure rewrite needed for - the 3 new Affine-only constructors); restoration tracked as L2-β. - `Counterexample.bad_input_untypable_l1` generalised to `forall m, - ~ has_type_l1 m ...` — Qed under both indices. -- **L3 — Echo / residue** (PRs #166, #167-line, parallel track): - `formal/Echo.v` scaffold with `Mode + LEcho + decoration-commuting - weakening`; `EchoR` residue + no-section irreversibility headline. - Decouples residue layer from typing layer; couples through L2's - thin-poset structure. -- **Disambiguation, durable**: `ephapax-affine ≠ AffineScript`. They are - separate languages sharing only the typed-wasm target. Per `README`, - `CLAUDE.md`, `.machine_readable/disambiguation.a2ml` hooks landed via - PRs #152 / affinescript#393 / typed-wasm#73. - -### Proof state (2026-05-20 → 2026-05-21) -- **Coq `preservation` reduction campaign**: 910 open goals → 12 (98.7% reduction). PR chain: - - **#92** — honest framing: replaced unsubstantiated "Qed 2026-04-27" claim with `Admitted.` - - **#102** — 910 → 29: standard preservation pattern (`remember (mu, R, e) as cfg`) - - **#104** — quantitative status correction in docs - - **#106** — 29 → 22: universal-IH revert (`revert mu R e mu' R' e' Hcfg Hcfg'` before `induction Hstep`) - - **#114** — region-invariance lemma `step_R_eq_or_touches_region` (Qed-closed; infrastructure) - - **#115** — corrected Idris2 region-linearity theorem names in docs - - **#116** — 22 → 12: per-case manual closures of 10 β-reduction / value-step constructors - - **#117** — canonicalised 5-phase closure plan in ROADMAP §"Preservation closure plan" - - **#121** — Phase 1 scaffold: `step_output_context_eq` (Lemma B) stated with induction skeleton - -- **Idris2 `%default partial → total` campaign** (2026-05-20): merged 9 PRs (#89, #90, #91, #93, #94, #96, #97, #99, #100) tightening totality across the parser / typechecker / IR-codec layer. 80+ atomic functions now provably total; zero `assert_total` / `believe_me` / `assert_smaller` introduced; 14 retained `covering` markers for documented Idris2 0.8.0 SCT limits. - -### Documentation -- **#113** — README + ROADMAP + EXPLAINME refactored for outside readers; documentation map; quickstart-by-audience; proof status tables. -- Wiki refresh — `Home.md` rewritten as nav hub; new `Proof-status.md`; 6 topic guides added (Linear-and-affine, Region-calculus, Two-phase-compiler, What-can-go-wrong, Comparison-to-other-languages, Glossary); `_Sidebar.md` for persistent nav. -- Repo description + topics — added `agda`, `idris2`, `webassembly`, `formal-verification`, `affine-types`, `region-calculus`, `compiler`, `operational-semantics`, `programming-language`. - -### Added -- **typed-wasm L7+L10 integration** (closes the typed-wasm-verify loop for ephapax): - - **C6 (#70)** — `ephapax-wasm` emits an `affinescript.ownership` custom section on every compile when any user fn has a Linear parameter. Section format and encoder come from the new `typed-wasm-verify` Rust crate (`hyperpolymath/typed-wasm:crates/typed-wasm-verify/`, rev `e11bb985` at C6, bumped to `67006edd` at C7). Driven by `Ty::is_linear()`; entries sorted by `func_idx` for deterministic output. 4 new unit tests; `cargo test --workspace` clean. - - **C7 (#72)** — New `--verify-ownership` flag on `ephapax compile`. After codegen and before output write, runs `typed_wasm_verify::verify_from_module` on the emitted module and surfaces any aliasing / linearity violation as a non-zero exit. Output: `✓ typed-wasm L7+L10 verification: clean` on success; per-violation diagnostics on failure. 3 new integration tests spawn the built binary. - - Net effect: ephapax-emitted wasm now has end-to-end L7+L10 guarantees — producer side (custom section emission) + opt-in consumer side (verifier flag), both backed by the canonical Rust verifier shared with `hyperpolymath/typed-wasm`. -- `module Qualified.Name` declarations (dotted module paths) -- `--` Haskell-style line comments (alongside existing `//` and `/* */`) -- Qualified names in imports: `import Foo.Bar.Baz` -- `linear type` and `affine type` modifiers on type definitions -- `const_decl`: module-level `let NAME = expr` bindings -- `named_record_type_def`: `Type = Constructor { field: Type, ... }` syntax -- `->` return type syntax (alongside existing `:`) -- `region name:` colon syntax (alongside `region name { }`) -- `Decl::Const` variant in the AST for module-level constants - -### Fixed -- Keyword word-boundary fix: identifiers like `init_result` no longer blocked by `in` keyword match - -### Changed -- Parser hardened: 80 production unwraps eliminated, proper ParseError propagation -- License headers: 13 files migrated from EUPL-1.2 to PMPL-1.0-or-later -- Test unwraps replaced with `.expect()` for better diagnostics - -## [0.8.0] - 2026-03-16 -- 80 examples across 8 categories -- Region-linear fusion type system -- `__ffi()` intrinsic for foreign function interface -- 15 compiler crates - -## [0.7.0] - 2026-02-15 -- Initial public release -- Linear type system with region annotations -- Pattern matching, modules, error handling -- REPL and interpreter - -[Unreleased]: https://github.com/hyperpolymath/ephapax/compare/v0.8.0...HEAD -[0.8.0]: https://github.com/hyperpolymath/ephapax/compare/v0.7.0...v0.8.0 -[0.7.0]: https://github.com/hyperpolymath/ephapax/releases/tag/v0.7.0 diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc new file mode 100644 index 00000000..963fe786 --- /dev/null +++ b/CODE_OF_CONDUCT.adoc @@ -0,0 +1,339 @@ +== Code of Conduct + +=== Our Pledge + +We as members, contributors, and leaders pledge to make participation in +ephapax a harassment-free experience for everyone, regardless of age, +body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, caste, colour, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, +welcoming, diverse, inclusive, and healthy community. + +We recognise that a thriving open source community requires +*psychological safety* — an environment where people can contribute, ask +questions, make mistakes, and learn without fear of ridicule or +retaliation. + +''''' + +=== Our Standards + +==== Expected Behaviour + +The following behaviours contribute to a positive environment: + +*Communication* - Using welcoming and inclusive language - Being +respectful of differing viewpoints and experiences - Giving and +gracefully accepting constructive feedback - Assuming good intent while +addressing impact - Communicating clearly and patiently, especially with +newcomers + +*Collaboration* - Focusing on what is best for the community - Showing +empathy and kindness toward other community members - Being +collaborative rather than competitive - Mentoring and supporting less +experienced contributors - Celebrating others’ contributions and +successes + +*Professionalism* - Accepting responsibility and apologising to those +affected by our mistakes - Learning from the experience and avoiding +repetition - Respecting others’ time and attention - Staying on topic in +project spaces - Following project guidelines and conventions + +*Accessibility* - Using plain language and avoiding unnecessary jargon - +Providing alt text for images and transcripts for audio/video - Being +patient with those using assistive technologies - Accommodating +different communication styles and needs - Recognising that not everyone +communicates the same way + +==== Unacceptable Behaviour + +The following behaviours are considered harassment and are unacceptable: + +*Harassment* - The use of sexualised language or imagery, and sexual +attention or advances of any kind - Trolling, insulting or derogatory +comments, and personal or political attacks - Public or private +harassment - Deliberate intimidation, stalking, or following (online or +in-person) - Unwelcome physical contact or simulated physical contact +(e.g., emoji) - Sustained disruption of talks, events, or online +discussions + +*Discrimination* - Discriminatory jokes and language - Posting or +threatening to post others’ personally identifying information +("`doxing`") - Advocating for, or encouraging, any of the above +behaviour - Microaggressions — subtle, often unintentional, +discriminatory comments or actions + +*Professional Misconduct* - Publishing others’ private information +without explicit permission - Misrepresenting affiliation or +contributions - Plagiarism or claiming credit for others’ work - +Retaliating against anyone who reports a Code of Conduct violation - +Other conduct which could reasonably be considered inappropriate in a +professional setting + +==== Grey Areas + +Some situations require judgement. When uncertain: + +* *Intent vs Impact*: Good intentions do not excuse harmful impact. +Focus on making things right. +* *Power Dynamics*: Those with more power (maintainers, employers, +experienced contributors) must be especially mindful of their impact. +* *Cultural Differences*: What’s acceptable varies by culture. When in +doubt, err on the side of caution and ask. +* *Humour*: Jokes at others’ expense are rarely funny to everyone. Punch +up, not down. + +''''' + +=== Scope + +This Code of Conduct applies within all community spaces, including: + +*Online Spaces* - Repository discussions, issues, and pull/merge +requests - Project chat channels (Matrix, Discord, Slack, IRC) - Mailing +lists and forums - Social media when representing the project - Video +calls and virtual meetings + +*In-Person Spaces* - Conferences, meetups, and events - Workshops and +training sessions - Any gathering where you represent the project + +*Representation* This Code of Conduct also applies when an individual is +officially representing the community in public spaces. Examples +include: + +* Using an official project email address +* Posting via an official social media account +* Acting as an appointed representative at an event +* Speaking on behalf of the project + +''''' + +=== Enforcement + +==== Reporting + +If you experience or witness unacceptable behaviour, or have any other +concerns, please report it as soon as possible. + +*How to Report* + +[width="99%",cols="30%,33%,37%",options="header",] +|=== +|Method |Details |Best For +|*Email* |j.d.a.jewell@open.ac.uk |Detailed reports, sensitive matters + +|*Private Message* |Contact any maintainer directly |Quick questions, +minor issues + +|*Anonymous Form* |[Link to form if available] |When you need anonymity +|=== + +*What to Include* + +* Your contact information (unless anonymous) +* Names/usernames of those involved +* Description of what happened +* When and where it occurred +* Any witnesses +* Any supporting evidence (screenshots, links) +* How you would like us to respond (if you have a preference) + +*What Happens Next* + +[arabic] +. You will receive acknowledgment within *48 hours* +. The Code of Conduct Committee will review the report +. We may ask for additional information +. We will determine appropriate action +. We will inform you of the outcome (respecting others’ privacy) + +==== Confidentiality + +All reports will be handled with discretion: + +* Reporter identity is protected by default +* Details are shared only with those who need to know +* We will ask before naming you in any communication +* Anonymous reports are accepted and investigated + +==== Conflicts of Interest + +If a Code of Conduct Committee member is involved in an incident: + +* They will recuse themselves from the process +* Another maintainer or external party will handle the report +* We will disclose any potential conflicts + +''''' + +=== Enforcement Guidelines + +The Code of Conduct Committee will follow these guidelines in +determining consequences: + +==== 1. Correction + +*Community Impact*: Use of inappropriate language or other behaviour +deemed unprofessional or unwelcome. + +*Consequence*: A private, written warning providing clarity around the +nature of the violation and an explanation of why the behaviour was +inappropriate. A public apology may be requested. + +*Duration*: Immediate + +==== 2. Warning + +*Community Impact*: A violation through a single incident or series of +actions. + +*Consequence*: A warning with consequences for continued behaviour. No +interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, for a specified period. This +includes avoiding interactions in community spaces as well as external +channels like social media. Violating these terms may lead to a +temporary or permanent ban. + +*Duration*: 1-4 weeks + +==== 3. Temporary Ban + +*Community Impact*: A serious violation of community standards, +including sustained inappropriate behaviour. + +*Consequence*: A temporary ban from any sort of interaction or public +communication with the community for a specified period. No public or +private interaction with the people involved, including unsolicited +interaction with those enforcing the Code of Conduct, is allowed during +this period. Violating these terms may lead to a permanent ban. + +*Duration*: 1-6 months + +==== 4. Permanent Ban + +*Community Impact*: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behaviour, harassment of an +individual, or aggression toward or disparagement of classes of +individuals. + +*Consequence*: A permanent ban from any sort of public interaction +within the community. + +*Duration*: Permanent (with appeal rights after 12 months) + +==== Enforcement Across Perimeters + +For contributors with elevated access (Perimeter 2 or 1): + +[cols=",",options="header",] +|=== +|Level |Additional Consequence +|Correction |Noted in contributor record +|Warning |Access privileges may be temporarily reduced +|Temporary Ban |Access reduced to Perimeter 3 for ban duration +|Permanent Ban |All access revoked +|=== + +''''' + +=== Appeals + +If you believe an enforcement decision was made in error: + +[arabic] +. *Wait 7 days* after the decision (cooling-off period) +. *Email* j.d.a.jewell@open.ac.uk with subject line "`Appeal: [Original +Report ID]`" +. *Explain* why you believe the decision should be reconsidered +. *Provide* any new information not previously available + +*Appeals Process* + +* Appeals are reviewed by a different Code of Conduct Committee member +than the original +* You will receive a response within 14 days +* The appeals decision is final +* You may only appeal once per incident + +*Grounds for Appeal* + +* Procedural errors in the original investigation +* New evidence not previously available +* Disproportionate response to the violation +* Misunderstanding of facts + +''''' + +=== Supporting Those Who Report + +We are committed to supporting those who report violations: + +*We Will* - Believe and take all reports seriously - Respect your +privacy and confidentiality preferences - Keep you informed of progress +(if you wish) - Take steps to protect you from retaliation - Provide +resources if you need support + +*We Will Not* - Require you to confront the person directly - Dismiss +reports without investigation - Reveal your identity without consent - +Tolerate retaliation against reporters - Rush you to make decisions + +''''' + +=== Prevention + +Beyond enforcement, we actively work to prevent issues: + +*Onboarding* - All contributors are expected to read this Code of +Conduct - Perimeter 2 applicants must confirm they’ve read and +understood it - Maintainers receive additional training on enforcement + +*Culture* - We model the behaviour we expect - We intervene early when +we see potential issues - We thank people for positive contributions - +We create opportunities for diverse voices + +*Review* - This Code of Conduct is reviewed annually - Community +feedback is welcomed - Changes are communicated clearly + +''''' + +=== Acknowledgments + +This Code of Conduct is adapted from: + +* https://www.contributor-covenant.org/[Contributor Covenant], version +2.1 +* https://www.djangoproject.com/conduct/[Django Code of Conduct] +* https://www.rust-lang.org/policies/code-of-conduct[Rust Code of +Conduct] +* https://www.python.org/psf/conduct/[Python Community Code of Conduct] + +We thank these communities for their leadership in creating welcoming +spaces. + +''''' + +=== Questions? + +If you have questions about this Code of Conduct: + +* Open a +https://github.com/hyperpolymath/ephapax/discussions[Discussion] (for +general questions) +* Email j.d.a.jewell@open.ac.uk (for private questions) +* Contact any maintainer directly + +''''' + +=== Summary + +*Be kind. Be respectful. Be collaborative.* + +We’re all here because we care about this project. Let’s make it a place +where everyone can do their best work. + +''''' + +Last updated: 2026 · Based on Contributor Covenant 2.1 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 27dda8ae..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,308 +0,0 @@ -# Code of Conduct - - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in ephapax a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, colour, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -We recognise that a thriving open source community requires **psychological safety** — an environment where people can contribute, ask questions, make mistakes, and learn without fear of ridicule or retaliation. - ---- - -## Our Standards - -### Expected Behaviour - -The following behaviours contribute to a positive environment: - -**Communication** -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Giving and gracefully accepting constructive feedback -- Assuming good intent while addressing impact -- Communicating clearly and patiently, especially with newcomers - -**Collaboration** -- Focusing on what is best for the community -- Showing empathy and kindness toward other community members -- Being collaborative rather than competitive -- Mentoring and supporting less experienced contributors -- Celebrating others' contributions and successes - -**Professionalism** -- Accepting responsibility and apologising to those affected by our mistakes -- Learning from the experience and avoiding repetition -- Respecting others' time and attention -- Staying on topic in project spaces -- Following project guidelines and conventions - -**Accessibility** -- Using plain language and avoiding unnecessary jargon -- Providing alt text for images and transcripts for audio/video -- Being patient with those using assistive technologies -- Accommodating different communication styles and needs -- Recognising that not everyone communicates the same way - -### Unacceptable Behaviour - -The following behaviours are considered harassment and are unacceptable: - -**Harassment** -- The use of sexualised language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Deliberate intimidation, stalking, or following (online or in-person) -- Unwelcome physical contact or simulated physical contact (e.g., emoji) -- Sustained disruption of talks, events, or online discussions - -**Discrimination** -- Discriminatory jokes and language -- Posting or threatening to post others' personally identifying information ("doxing") -- Advocating for, or encouraging, any of the above behaviour -- Microaggressions — subtle, often unintentional, discriminatory comments or actions - -**Professional Misconduct** -- Publishing others' private information without explicit permission -- Misrepresenting affiliation or contributions -- Plagiarism or claiming credit for others' work -- Retaliating against anyone who reports a Code of Conduct violation -- Other conduct which could reasonably be considered inappropriate in a professional setting - -### Grey Areas - -Some situations require judgement. When uncertain: - -- **Intent vs Impact**: Good intentions do not excuse harmful impact. Focus on making things right. -- **Power Dynamics**: Those with more power (maintainers, employers, experienced contributors) must be especially mindful of their impact. -- **Cultural Differences**: What's acceptable varies by culture. When in doubt, err on the side of caution and ask. -- **Humour**: Jokes at others' expense are rarely funny to everyone. Punch up, not down. - ---- - -## Scope - -This Code of Conduct applies within all community spaces, including: - -**Online Spaces** -- Repository discussions, issues, and pull/merge requests -- Project chat channels (Matrix, Discord, Slack, IRC) -- Mailing lists and forums -- Social media when representing the project -- Video calls and virtual meetings - -**In-Person Spaces** -- Conferences, meetups, and events -- Workshops and training sessions -- Any gathering where you represent the project - -**Representation** -This Code of Conduct also applies when an individual is officially representing the community in public spaces. Examples include: - -- Using an official project email address -- Posting via an official social media account -- Acting as an appointed representative at an event -- Speaking on behalf of the project - ---- - -## Enforcement - -### Reporting - -If you experience or witness unacceptable behaviour, or have any other concerns, please report it as soon as possible. - -**How to Report** - -| Method | Details | Best For | -|--------|---------|----------| -| **Email** | j.d.a.jewell@open.ac.uk | Detailed reports, sensitive matters | -| **Private Message** | Contact any maintainer directly | Quick questions, minor issues | -| **Anonymous Form** | [Link to form if available] | When you need anonymity | - -**What to Include** - -- Your contact information (unless anonymous) -- Names/usernames of those involved -- Description of what happened -- When and where it occurred -- Any witnesses -- Any supporting evidence (screenshots, links) -- How you would like us to respond (if you have a preference) - -**What Happens Next** - -1. You will receive acknowledgment within **48 hours** -2. The Code of Conduct Committee will review the report -3. We may ask for additional information -4. We will determine appropriate action -5. We will inform you of the outcome (respecting others' privacy) - -### Confidentiality - -All reports will be handled with discretion: - -- Reporter identity is protected by default -- Details are shared only with those who need to know -- We will ask before naming you in any communication -- Anonymous reports are accepted and investigated - -### Conflicts of Interest - -If a Code of Conduct Committee member is involved in an incident: - -- They will recuse themselves from the process -- Another maintainer or external party will handle the report -- We will disclose any potential conflicts - ---- - -## Enforcement Guidelines - -The Code of Conduct Committee will follow these guidelines in determining consequences: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behaviour deemed unprofessional or unwelcome. - -**Consequence**: A private, written warning providing clarity around the nature of the violation and an explanation of why the behaviour was inappropriate. A public apology may be requested. - -**Duration**: Immediate - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of actions. - -**Consequence**: A warning with consequences for continued behaviour. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. - -**Duration**: 1-4 weeks - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including sustained inappropriate behaviour. - -**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. - -**Duration**: 1-6 months - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behaviour, harassment of an individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the community. - -**Duration**: Permanent (with appeal rights after 12 months) - -### Enforcement Across Perimeters - -For contributors with elevated access (Perimeter 2 or 1): - -| Level | Additional Consequence | -|-------|----------------------| -| Correction | Noted in contributor record | -| Warning | Access privileges may be temporarily reduced | -| Temporary Ban | Access reduced to Perimeter 3 for ban duration | -| Permanent Ban | All access revoked | - ---- - -## Appeals - -If you believe an enforcement decision was made in error: - -1. **Wait 7 days** after the decision (cooling-off period) -2. **Email** j.d.a.jewell@open.ac.uk with subject line "Appeal: [Original Report ID]" -3. **Explain** why you believe the decision should be reconsidered -4. **Provide** any new information not previously available - -**Appeals Process** - -- Appeals are reviewed by a different Code of Conduct Committee member than the original -- You will receive a response within 14 days -- The appeals decision is final -- You may only appeal once per incident - -**Grounds for Appeal** - -- Procedural errors in the original investigation -- New evidence not previously available -- Disproportionate response to the violation -- Misunderstanding of facts - ---- - -## Supporting Those Who Report - -We are committed to supporting those who report violations: - -**We Will** -- Believe and take all reports seriously -- Respect your privacy and confidentiality preferences -- Keep you informed of progress (if you wish) -- Take steps to protect you from retaliation -- Provide resources if you need support - -**We Will Not** -- Require you to confront the person directly -- Dismiss reports without investigation -- Reveal your identity without consent -- Tolerate retaliation against reporters -- Rush you to make decisions - ---- - -## Prevention - -Beyond enforcement, we actively work to prevent issues: - -**Onboarding** -- All contributors are expected to read this Code of Conduct -- Perimeter 2 applicants must confirm they've read and understood it -- Maintainers receive additional training on enforcement - -**Culture** -- We model the behaviour we expect -- We intervene early when we see potential issues -- We thank people for positive contributions -- We create opportunities for diverse voices - -**Review** -- This Code of Conduct is reviewed annually -- Community feedback is welcomed -- Changes are communicated clearly - ---- - -## Acknowledgments - -This Code of Conduct is adapted from: - -- [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1 -- [Django Code of Conduct](https://www.djangoproject.com/conduct/) -- [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) -- [Python Community Code of Conduct](https://www.python.org/psf/conduct/) - -We thank these communities for their leadership in creating welcoming spaces. - ---- - -## Questions? - -If you have questions about this Code of Conduct: - -- Open a [Discussion](https://github.com/hyperpolymath/ephapax/discussions) (for general questions) -- Email j.d.a.jewell@open.ac.uk (for private questions) -- Contact any maintainer directly - ---- - -## Summary - -**Be kind. Be respectful. Be collaborative.** - -We're all here because we care about this project. Let's make it a place where everyone can do their best work. - ---- - -Last updated: 2026 · Based on Contributor Covenant 2.1 diff --git a/GOVERNANCE.adoc b/GOVERNANCE.adoc index e41020d3..9b836fb2 100644 --- a/GOVERNANCE.adoc +++ b/GOVERNANCE.adoc @@ -1,162 +1,60 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Governance Model -:toc: preamble +== Governance -This document describes the governance model for this repository. +=== Overview -== Overview +This project is governed by the following principles and structures to +ensure transparent, inclusive, and effective decision-making. -This repository follows a **Sole Maintainer Governance Model**: +=== Roles and Responsibilities -* Single maintainer (@hyperpolymath) has full authority over the project -* All contributions are welcome and reviewed by the maintainer -* Decisions are made transparently through GitHub issues and discussions -* The project adheres to the hyperpolymath estate policies where applicable +==== Maintainers -== Core Principles +Maintainers are responsible for: - Reviewing and merging pull requests - +Managing releases and versioning - Ensuring code quality and standards - +Triaging issues and bug reports - Community engagement and support -[cols="1,2"] -|=== -| Principle | Description +==== Contributors -| **Benevolent Dictatorship** | Maintainer has final decision authority but seeks community input +Contributors are expected to: - Follow the code of conduct - Submit +well-documented pull requests - Write tests for new functionality - +Maintain existing tests - Update documentation as needed -| **Meritocracy** | Contributions are judged on technical merit, not contributor identity +=== Decision Making -| **Transparency** | All significant decisions are documented publicly +==== Minor Changes -| **Consensus-Seeking** | Maintainer prefers consensus but will decide when necessary +* Can be made by any maintainer +* Include bug fixes, documentation updates, dependency updates -| **Open Contribution** | Anyone can contribute via fork and pull request +==== Major Changes -|=== +* Require discussion in issues or pull requests +* Include new features, architectural changes, API changes +* Need approval from at least 2 maintainers -== Roles and Permissions +==== Breaking Changes -[cols="1,2,2"] -|=== -| Role | Permissions | Assignment +* Require RFC (Request for Comments) process +* Need approval from majority of maintainers +* Must include migration guide -| **Maintainer** | Write access, merge rights, admin | @hyperpolymath -| **Contributors** | Read access, fork, submit PRs | All GitHub users -| **Users** | Use the software, report issues | All GitHub users +=== Code of Conduct -|=== +All participants are expected to follow our Code of Conduct. Violations +can be reported to the maintainers. -== Decision Making Framework +=== Communication -=== Routine Decisions +* *Issues*: For bug reports and feature requests +* *Discussions*: For questions and general discussion +* *Pull Requests*: For code contributions -* Bug fixes -* Documentation improvements -* Minor feature additions -* Dependency updates +=== Licensing -**Process**: Maintainer reviews and merges PRs that meet quality standards. +All contributions are made under the terms of the repository’s LICENSE +file. By submitting a pull request, you agree to license your +contributions accordingly. -=== Significant Changes +''''' -* New major features -* API changes -* Architecture modifications -* Breaking changes - -**Process**: -. Open issue describing the change -. Discuss with community (minimum 72 hours) -. Maintainer makes final decision -. Document rationale in issue/PR - -=== Structural Decisions - -* Repository purpose/renaming -* License changes -* Ownership transfer -* Deprecation/archival - -**Process**: -. Extended discussion (minimum 1 week) -. Maintainer makes final decision -. Document in CHANGELOG and governance docs - -== Contribution Lifecycle - -[cols="1,2"] -|=== -| Stage | Process - -| **Ideation** | Open issue, discuss feasibility - -| **Development** | Fork, implement, test thoroughly - -| **Review** | Submit PR, maintainer reviews within 7 days - -| **Merge** | Maintainer merges or requests changes - -| **Release** | Maintainer publishes according to project conventions - -|=== - -== Conflict Resolution - -In case of disagreements: - -. Discuss in the relevant GitHub issue or PR -. Provide technical justification for positions -. Maintainer mediates and makes final decision -. Decision is documented and can be revisited later - -== Project Policies - -This repository adheres to hyperpolymath estate-wide policies: - -* **License**: MPL-2.0 for code, CC-BY-SA-4.0 for prose (per standards/LICENCE-POLICY.adoc) -* **Code of Conduct**: Follows hyperpolymath CODE_OF_CONDUCT.md -* **Security**: Follows hyperpolymath SECURITY.md -* **Contributing**: Follows hyperpolymath CONTRIBUTING.adoc conventions - -== Repository-Specific Conventions - -[cols="1,2"] -|=== -| Convention | Description - -| **Signing** | All commits must be signed (SSH or GPG) - -| **SPDX Headers** | All source files must have SPDX license identifiers - -| **Contractiles** | Mustfile, Trustfile, Intendfile, Adjustfile in root - -| **Machine Readable** | META.a2ml in .machine_readable/6a2/ - -| **CI/CD** | GitHub Actions workflows in .github/workflows/ - -|=== - -== Governance Evolution - -As the project grows, this governance model may evolve: - -* **Adding Co-Maintainers**: When contribution volume warrants it -* **Forming a Team**: For complex multi-maintainer projects -* **Adopting TPCF**: For large, multi-repository projects (see rhodium-standard-repositories) - -Changes to this document require the same process as Significant Changes above. - -== See Also - -* link:MAINTAINERS.adoc[Maintainers] -* link:CODE_OF_CONDUCT.md[Code of Conduct] -* link:CONTRIBUTING.adoc[Contributing Guide] -* link:https://github.com/hyperpolymath/standards/blob/main/LICENCE-POLICY.adoc[Estate License Policy] -* link:https://github.com/hyperpolymath/standards[rhodium-standard-repositories (TPCF)] - -== Changelog - -[cols="1,1,1"] -|=== -| Date | Change | By - -| 2026-06-07 | Initial governance model established | @hyperpolymath -|=== +_Last updated: 2026-07-18_ diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index e27364c7..00000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1,60 +0,0 @@ -# Governance - -## Overview - -This project is governed by the following principles and structures to ensure transparent, inclusive, and effective decision-making. - -## Roles and Responsibilities - -### Maintainers - -Maintainers are responsible for: -- Reviewing and merging pull requests -- Managing releases and versioning -- Ensuring code quality and standards -- Triaging issues and bug reports -- Community engagement and support - -### Contributors - -Contributors are expected to: -- Follow the code of conduct -- Submit well-documented pull requests -- Write tests for new functionality -- Maintain existing tests -- Update documentation as needed - -## Decision Making - -### Minor Changes -- Can be made by any maintainer -- Include bug fixes, documentation updates, dependency updates - -### Major Changes -- Require discussion in issues or pull requests -- Include new features, architectural changes, API changes -- Need approval from at least 2 maintainers - -### Breaking Changes -- Require RFC (Request for Comments) process -- Need approval from majority of maintainers -- Must include migration guide - -## Code of Conduct - -All participants are expected to follow our Code of Conduct. Violations can be reported to the maintainers. - -## Communication - -- **Issues**: For bug reports and feature requests -- **Discussions**: For questions and general discussion -- **Pull Requests**: For code contributions - -## Licensing - -All contributions are made under the terms of the repository's LICENSE file. -By submitting a pull request, you agree to license your contributions accordingly. - ---- - -*Last updated: 2026-07-18* diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc new file mode 100644 index 00000000..b8782aa6 --- /dev/null +++ b/PROOF-NEEDS.adoc @@ -0,0 +1,884 @@ +== Proof needs — Ephapax (linear + affine sublanguages) + +____ +=== 🛑 First-read disambiguation (human + machine readers) + +*This document is for `+hyperpolymath/ephapax+`.* Ephapax is a research +language for compile-time WebAssembly memory safety, formally verified +in Coq + Idris2. + +*Ephapax is NOT `+hyperpolymath/affinescript+`.* AffineScript is a +separate, unrelated language (JS/TS/AffineScript successor, OCaml + +AffineScript runtime, no mechanised proofs, no first-class regions). The +two share only the compile target (`+hyperpolymath/typed-wasm+`). + +*`+ephapax-affine+` (in this document) is the affine _sublanguage_ of +Ephapax* — the versatile prototyping companion to `+ephapax-linear+`. It +lives inside this repo (`+ephapax-linear/+` crate). *It is NOT +AffineScript.* The lexical overlap of the word `+affine+` is a +coincidence of substructural-logic terminology, not a project +relationship. Do not apply lessons or tactics across the boundary. + +If you found this document by searching for "`affine type system`", +verify which language you actually need before proceeding. Canonical +side-by-side table: +https://github.com/hyperpolymath/nextgen-languages/blob/main/docs/disambiguation/ephapax-vs-affinescript.md +____ + +=== Reading guide + +This file is the _proof-debt audit_ for Ephapax post-counterexample +(2026-05-26). It supersedes earlier versions of `+PROOF-NEEDS.md+` that +still referenced the legacy preservation closure plan as if it were +viable. + +The audit is split by sublanguage: + +* *§1* — what’s done (mechanised, Qed) for each sublanguage. +* *§2* — what’s todo (active work, by layer). +* *§3* — what’s banned (explicit do-not-do anti-patterns). +* *§4* — counts and file-by-file map. + +For the architectural background see link:STATUS.adoc[`+STATUS.adoc+`] +and +link:formal/PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md+`]. + +''''' + +=== §1. What’s done + +==== ephapax-linear (strict / obligation-preserving sublanguage) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Component |File |Status +|L1 judgment (`+has_type_l1+`) |`+formal/TypingL1.v+` |100% — 0 admits, +2 Qed + +|L1 judgment indexed by modality `+m : Modality+` |`+formal/TypingL1.v+` +|landed via PRs #176 + #177 + +|L2 modality core (`+Modality.v+`, `+linear_to_affine+`) +|`+formal/Modality.v+` |1 Qed, zero axioms + +|L3 calculus (echo / residue fiber + degrade + no-section proof) +|`+formal/Echo.v+` |12 Qed, 0 admits + +|Linear-mode forward progress lemmas |`+formal/Semantics_L1.v+` |37 Qed; +*2* outer `+Admitted.+` markers (`+step_pop_disjoint_from_type_l1+` / +`+preservation_l1+`) cover *2* internal `+admit.+` cases at current +lines 3315/3336 (rebuilt `+coqc 8.18.0+` ground truth 2026-06-27; the +earlier 5-internal / lines-576/646/1994/2014/3132 figure is +pre-dissolution, superseded by the §4 marker = 3 total). Phase 3b Stage +1a (#252, MERGED 2026-05-30: `+tfuneff_lambda_free+` + +`+Counterexample_L2_nested.v+`) and Stage 1b (#253, MERGED 2026-05-31: +closed-value substitution + `+preservation_l2_app_eff_beta+` β-case for +TFunEff). Slice 4 L3 wiring delivered +`+preservation_l3_region_active_echo+` / `+preservation_l3_drop_echo+` / +`+preservation_l3+` umbrella. + +|L2 β-case lemmas (Stage 1b) |`+formal/TypingL2.v+` |10 Qed total: +`+weaken_modality+` family (5), `+preservation_l2_via_l1+` (conditional +on `+preservation_l1+`), `+linear_value_retype_l1_m+`, and 3 +`+preservation_l2_app_eff_beta_*+` variants (Linear / ground_nonlinear / +tfuneff conditional on Stage 1b side conditions P1+P2+P3). Stage 2/3/4 +(#240/#241/#242) drop the conditions. + +|L4 labelling scaffold (Phase A, 2026-05-28) |`+formal/L4.v+` +|`+ProgramMode+` enum (`+PModeLinear+` / `+PModeAffine+` / +`+PModeBoundaryMix+`) + `+program_mode_to_modality+` round-trip. +Definitions only — no theorems, zero axioms. + +|Counterexample_L2 regression witnesses (Phase 4c + 3b) +|`+formal/Counterexample_L2.v+`, `+formal/Counterexample_L2_nested.v+` +|5 + 5 Qed pinning the Phase 4c (fresh-region scope crossing) and Phase +3b (nested TFunEff) soundness-gap classes + +|Counterexample regression witness |`+formal/Counterexample.v+` |5 Qed +(`+bad_input_untypable_l1+` proved under both modes) + +|Operational checker (Rust, ephapax-linear sublanguage) +|`+ephapax-linear/src/linear.rs+` |working — discharges resource-exact +obligation +|=== + +==== ephapax-affine (relaxed / degradable sublanguage) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Component |File |Status +|Affine-mode judgment indexing |`+formal/TypingL1.v+` (`+m = Affine+` +branch) |landed; 3 Affine-only rules `+T_Lam_L1_Affine+`, +`+T_Case_L1_Affine+`, `+T_If_L1_Affine+` + +|Linear ⇒ Affine weakening |`+formal/TypingL1.v+` `+linear_to_affine+` +|Qed, zero axioms + +|Operational checker (Rust, ephapax-affine sublanguage) +|`+ephapax-linear/src/affine.rs+` |working — permits weakening / +graceful abandonment + +|Affine-mode echo discipline (LEcho Affine = lowered triple) +|`+formal/Echo.v+` (calculus) |calculus done; rule wiring pending + +|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; Phase 3b Stage 1a + 1b landed +2026-05-30/31 via #252 + #253. Remaining admits are pre-existing L1 +structural debt + one provably-false-as-stated sub-case +(`+Semantics_L1.v:1994+` / mirror `+:2014+` — closure requires Phase D +reformulation, not direct proof). See §4 seam audit. +|=== + +==== Counterexample regression + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Lemma |Status |What it shows +|`+bad_input_untypable_l1+` |Qed |the configuration that +steps-and-untypes is rejected at type check under the new L1 judgment, +in both Linear and Affine modes + +|4 supporting lemmas |Qed |the legacy judgment _types_ the same +configuration +|=== + +→ The counterexample establishes the gap. The new L1 judgment closes it. + +''''' + +=== §2. What’s todo + +==== Near-term (L1 ⇒ L2 integration debt — mechanical) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Item |File |Estimate +|✅ 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 + +|✅ Generalise typing_preserves_length_l1 to modality-polymorphic +|`+formal/Semantics_L1.v+` |done 2026-05-27 + +|✅ 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) + +|✅ 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) + +|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 + +|State and prove `+preservation_l1+` for both modes +|`+formal/Semantics_L1.v+` |depends on region_shrink + region_liveness +narrow admit +|=== + +==== Near-term (L3 wiring — design + mechanisation) + +L3 calculus is done in `+formal/Echo.v+` (12 Qed, no admits). What’s +missing is the _integration_ into the typing judgment and the step +rules. The shape is laid out in `+formal/PRESERVATION-DESIGN.md §6.3+` +"`Where echo enters the typing rules`" and (for the diagram) in §6 (to +be added). + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Item |What it does |Notes +|✅ 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) + +|✅ 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) + +|✅ 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) + +|✅ 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). Initial +3 internal admits in +[region_shrink_preserves_typing_l1_gen_m] + +[region_liveness_at_split_l1_gen] were parallel-rule MIRRORS of +pre-existing structural admits; the avoidable T_Region_L1_Echo mirror +was CLOSED in slice 4 — only the two true T_Region_Active_*_L1_Echo +shadowed-case mirrors remain, both blocked by the same pre-existing +list-vs-multiset structural debt as the originals + +|✅ 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 + +|[line-through]#Thread `+G+` (echo context) alongside `+R+` (region +context) through compound rules# |[line-through]#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. + +|✅ State and prove `+preservation_l3+` |Per-layer preservation theorem +against the L3 invariants for the new echo-emitting step rules + +echo-typed paths |done 2026-05-27 (L3 wiring slice 4 — capstone). Two +per-case Qed lemmas (`+preservation_l3_region_active_echo+` for +`+S_Region_Exit_Echo+` × `+T_Region_Active_L1_Echo+`, and +`+preservation_l3_drop_echo+` for `+S_Drop_Echo+` × `+T_Drop_L1_Echo+`) +plus an umbrella `+preservation_l3+` (their conjunction, Qed). Zero new +admits or axioms. Per-case alignment forced by `+T_Echo_L1+`’s +witness-type premise; non-deterministic crossover cases are +non-preserving by design (typing derivation pins the path). +Conditionally Qed under the pre-existing +`+region_shrink_preserves_typing_l1_gen_m+` L1 structural admit per +PRESERVATION-DESIGN.md §5.1. + +|⏸ Register `+TEcho+` as linear via `+is_linear_ty+` (Phase B Slice 1) — +DEFERRED 2026-05-28 |Realises the design intent annotated at +`+TypingL1.v:T_Observe_L1+`: under Linear discipline, unobserved +`+TEcho+` must fail typing closure |*deferred until Phase D*. +Investigation 2026-05-28: adding `+| TEcho _ => true+` to +`+is_linear_ty+` is structurally non-vacuous at L1 — `+EEcho T v+` is +both a value (`+VEcho+`) and typed at `+TEcho T+` (via `+T_Echo_L1+`, +TypingL1.v:351-354), refuting the conclusion of +`+linear_value_is_loc_l1+` (Semantics_L1.v:891) and breaking 9 +call-sites in `+subst_typing_gen_l1_m+` (Semantics_L1.v:1326-1605, Qed). +Realising the wire requires a disjunctive rewrite of +`+linear_value_is_loc_l1+` plus per-caller handling of the TEcho +disjunct — substantially larger than the original "`1-line +definitional`" framing. Owner deferred to after Phase D (L2 effect-typed +TFun) when the L1 substitution chain reshapes naturally. The legacy +`+Semantics.v+` branch is vacuous (no rule produces `+TEcho+`) and would +have closed trivially. + +|⏸ Close the L1 list-vs-multiset structural admits at +`+Semantics_L1.v:553/621+` (Phase C) — DEFERRED 2026-05-28 |The +`+T_Region_Active_L1+`-shadowed sub-case of +`+region_shrink_preserves_typing_l1_gen_m+` requires bridging body +derivations across input shrinkage when the outer region matches a +body-internal active occurrence |*deferred until Phase D*. Four design +paths surfaced + analytically evaluated 2026-05-28 (recorded in this +session’s memory + companion design note `+Semantics_L1.v:366-402+`): +(a) Permutation-based perm-l1 leaves a list-structure output mismatch +(concrete counterexample at `+R_body = [a; rr; b; c]+` vs +`+R_body' = [b; rr; a; c]+`); (b) multiset reformulation of +`+remove_first_L1+` cascades through every L1 rule’s output threading; +(c) `+T_Region_*_L1+` rule redesign (Permutation premise on body output) +was implemented in-flight on `+proof/phase-c-l1-multiset-bridge+` — +closes sub-sub-case (ii) (R has exactly 1 rr via count-vacuity) but +leaves sub-sub-case (i) (R has ≥2 rr) requiring body-input-shrinkage, +which itself is NOT a theorem (T_Loc_L1 counterexample at count=1; T_Let +composition fails for the count-≥-2 precondition); (d) defer was +selected as the most mathematically safe + elegant path. Phase D (L2 +effect-typed TFun) introduces the natural setting: lambda bodies’ R-flow +becomes effect-typed, which gives the structural invariant that lets +body-input-shrinkage discharge. The 2 admits at lines 553/621 remain as +documented L1 debt, and the outer `+Admitted.+` at line 653 + the +`+preservation_l3+` dependency annotation in PRESERVATION-DESIGN.md §5.1 +continue to attribute them correctly. +|=== + +==== Mid-term (L4 — scaffold landed 2026-05-28) + +L4 covers dyadic interaction semantics (mother–child distinction). Per +`+formal/PRESERVATION-DESIGN.md §7+`, L4 is "`not a separate proof +layer. It is a labelling discipline at the program / module level… No +proofs change.`" + +Phase A scaffold landed 2026-05-28: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Item |File |Status +|✅ L4 design page |`+formal/L4-DYADIC.md+` |done — extracts + extends +design doc §7 + +|✅ Labelling enum `+ProgramMode+` + `+program_mode_to_modality+` +mapping to L2 |`+formal/L4.v+` |done — Definitions only, no theorems, no +admit, no axiom +|=== + +Future L4 work (post-Phase D, optional): surface-syntax wiring in the +borrow checker so `+#![ephapax_linear]+` / `+#![ephapax_affine]+` / +`+#![module_boundary_mix]+` parse and select the L2 modality. This is +implementation, not proof debt. + +==== Value-prop directions (committed and speculative) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Direction |Status |Proof obligation +|Linear Echo + Region-Based Memory Management (S_Region_Exit +certified-evidence emission) |Committed for v1 |`+preservation_l3+` for +the Region branch + +|Selective reversibility via L2 modality (Linear ≤ Affine thin-poset) +|Committed for v1 |Already mechanised in `+Modality.v+` + +|Debugging / provenance via Linear Echoes as type-system receipts +|Committed for v1 |Follows from L3 wiring + +|Linear discipline on deterministic drop (Rust-style) for critical +resources |Speculative |Compatible with L3 wiring; not committed v1 + +|GDPR-style certified erasure as compliance use-case |Direction +|Concrete instantiation; no separate proof obligation +|=== + +''''' + +=== §3. What’s NOT to do (banned anti-patterns) + +These are not preferences. They are explicit, durable, owner-issued +2026-05-27 directives. + +==== A. Do not close the legacy preservation theorem + +* ❌ `+Theorem preservation+` in `+formal/Semantics.v+` is *provably +false*. No proof closes it. The `+Admitted.+` is correct. Leave it. +* ❌ Do not add lemmas to `+formal/Semantics.v+` aimed at that closure. +Examples that have wasted real session time: +** Lemma B variants (`+step_preserves_type+`, +`+step_output_context_eq+`, `+step_preserves_type_at_pre+`, +`+step_output_context_eq_at_pre+`) +** `+region_add_typing_for_non_values+` +** Strengthened substitution lemmas +** `+sibling_typing_transfers_across_step+`-style helpers +* ❌ Do not patch the legacy `+formal/Typing.v+` judgment. +`+Counterexample.v+` depends on its falsity. + +==== B. Do not close L1/L2 admits by ad-hoc cross-layer reasoning + +* ❌ `+region_liveness_at_split_l1+` (if it returns) must NOT be closed +by adding proof tricks, side conditions discharged ad-hoc, or +strengthened lemma signatures within the current judgment shape. +Post-L2-hybrid these closures are cross-layer; the layered design +intentionally splits some closures across layers. +* ❌ The 9 Semantics_L1.v admits are L2-integration debt (bullet +structure for new Affine constructors). They are _not_ invitations to +introduce new `+Axiom+` declarations. + +==== C. Do not bake linearity into Echo Types + +Echo semantics (L3) and structural discipline (L2) must remain +orthogonal and compositional. + +* ❌ Do not write `+LinearEcho+` and `+AffineEcho+` as separate type +formers. There is *one* type former (the fiber); the two modes are +_applications_ of it with different witness shapes. +* ❌ Do not pattern-match on `+Linear+`/`+Affine+` inside `+Echo.v+`. +The discipline is read from L2’s `+m : Modality+` at the typing-rule +boundary, not inside the calculus. + +==== D. Do not treat Echo Types as a tracing-GC replacement + +* ❌ Echo Types do not solve reachability or cycles. +* ❌ Echo Types are not a "`fire-and-forget`" automatic memory manager. +* ✅ Echo Types are a _type-theoretic discipline for accountability of +irreversible reclamation_ — strongest in conjunction with RBMM (regions) +or deterministic ownership (Rust-style drop). + +==== E. Do not follow pre-2026-05-26 plans + +* ❌ Anything dated before 2026-05-26 that proposes closing +`+preservation+` to `+Qed.+` is pre-discovery. PRs #92, #102, #104, +#106, #114, #116, #117, #121, #146 are archaeology. +* ❌ Branch `+lemma-b-phase2-middle-narrow+` (deleted post-archaeology +cherry-pick) was the most recent pre-discovery attempt. Do not +resurrect. + +==== F. Do not conflate `+ephapax-affine+` with AffineScript + +* ❌ `+ephapax-affine+` lives in this repo’s `+ephapax-linear/+` crate +alongside `+ephapax-linear+`. It is a _sublanguage_ of Ephapax. +* ❌ `+AffineScript+` (`+hyperpolymath/affinescript+`) is a different +language with no source-level overlap. Borrow-checker tactics from +`+affinescript/lib/borrow.ml+` do not apply here, and vice versa. +* ❌ The lexical overlap of the word `+affine+` is a coincidence. +Substructural-logic family terminology, not project relationship. + +==== Anti-pattern detector + +If your session is producing any of the following, *stop and escalate to +the owner*: + +* `+sibling-region-disjointness+` side conditions on compound rules +* region-weakening predicates indexed on syntactic shape +* admit-shuffling between `+Semantics.v+` and a new lemma file +* "`the previous-PR-line-of-attack just needs one more lemma`" +* proposing to close `+Theorem preservation+` in `+Semantics.v+` to +`+Qed.+` by any chain of reasoning +* adding new `+Axiom+` declarations to discharge L1/L2 gaps +* writing a `+LinearEcho+` distinct from an `+AffineEcho+` +* applying tactics or framings from `+hyperpolymath/affinescript+` here + +''''' + +=== §4. Counts + file-by-file map + +Coq admitted proofs remaining: 3 + +(1 outer `+Admitted.+` in `+formal/Semantics.v+` — sacrosanct legacy +preservation, provably false per `+Counterexample.v+` + *2* outer +`+Admitted.+` markers in `+formal/Semantics_L1.v+` — the two open L1 +lemmas `+step_pop+` / `+preservation_l1+`. *Updated 2026-06-26 (this +branch) against rebuilt `+coqc 8.18.0+` + +`+scripts/status-gate.sh --proofs+` ground truth. Two closures dropped +the count 5 → 3:* *(a) `+region_shrink_preserves_typing_l1_gen_m+` was +_dissolved_ (the 2026-06-16 multiset-perm closure) — its general form is +proven false and pinned in `+formal/Counterexample_RegionShrink.v+`, the +surviving value corollary `+region_shrink_value+` is `+Qed+`/axiom-free, +and `+preservation_l3+` is consequently UNCONDITIONALLY axiom-free.* +*(b) `+region_liveness_at_split_l1_gen+` — the provably-false +`+In rv R -> In rv R'+` lemma — was _removed_. Its 13 consumers in +`+subst_typing_gen_l1_m+` now route through the TRUE, `+Qed+` +`+region_liveness_no_exit_l1_gen+`, supplied with the honest +`+val_region_no_exit+` premise threaded through +`+subst_typing_gen_l1_m+` / `+subst_typing_gen_l1+` / +`+subst_preserves_typing_l1+` and the L2 β-case lemmas +(`+preservation_l2_app_eff_beta_linear+` / `+_l1+`) — all of which +`+Print Assumptions+` now reports as "`Closed under the global context`" +(axiom-free) instead of depending on a false axiom.* The two remaining +outer markers are `+step_pop_disjoint_from_type_l1+` and +`+preservation_l1+` (capstone, gated on `+step_pop+`); §5’s line numbers +are earlier-branch snapshots, superseded here.) + +==== Per-file Qed / Admitted summary (as of 2026-06-01) + +[width="100%",cols="23%,>28%,>28%,21%",options="header",] +|=== +|File |Qed |Admitted |Disposition +|`+formal/Semantics.v+` (legacy) |n/a |*1* |🛑 archaeology — single +`+Admitted.+` at line 9257 (`+Theorem preservation+`, provably false); +do not extend + +|`+formal/Typing.v+` (legacy) |n/a |0 |🛑 archaeology — +`+Counterexample.v+` depends on falsity + +|`+formal/Counterexample.v+` |*5* |0 |✅ pinned regression witness +(`+bad_typable+`, `+bad_step+`, `+bad_post_untypable+`, +`+t_loc_l1_R_preserving+`, `+bad_input_untypable_l1+`) + +|`+formal/Counterexample_L2.v+` |*5* |0 |✅ Phase 4c soundness-gap +witness — fresh-region scope crossing (`+v_typed_at_empty+`, +`+outer_typed+`, `+e_before_typed+`, `+e_step+`, `+e_after_untypable+`) + +|`+formal/Counterexample_L2_nested.v+` |*5* |0 |✅ Phase 3b +soundness-gap witness — nested TFunEff (analogue structure to +`+Counterexample_L2.v+`) + +|`+formal/TypingL1.v+` |*2* |0 |✅ active — L1 judgment, +modality-indexed + +|`+formal/Semantics_L1.v+` |*54* |*2* |✅ active — Phase 3b Stage 1a + +1b landed via PRs #252 + #253. *2* outer `+Admitted.+` markers — the two +open L1 lemmas (`+step_pop_disjoint_from_type_l1+` / +`+preservation_l1+`). *Updated 2026-06-26: `+region_shrink…gen_m+` +dissolved (2026-06-16) and `+region_liveness_at_split_l1_gen+` removed +(this branch) via the honest `+val_region_no_exit+` reformulation — see +the §4 marker note. `+preservation_l3+` and the L2 β-case lemmas are now +unconditionally axiom-free. Counts are rebuilt `+coqc 8.18.0+` ground +truth on this branch.* + +|`+formal/Modality.v+` |*1* |0 |✅ active — L2 core, zero axioms +(`+linear_to_affine+`) + +|`+formal/Echo.v+` |*12* |0 |✅ active — L3 calculus mechanised + +|`+formal/TypingL2.v+` |*10* |0 |✅ active — `+weaken_modality+` (+ +Affine_id + 3 `+_le_*+` variants), `+preservation_l2_via_l1+` +(conditional on `+preservation_l1+`), `+linear_value_retype_l1_m+`, and +3 β-case lemmas (`+preservation_l2_app_eff_beta_linear+`, +`+_ground_nonlinear+`, `+_tfuneff+` conditional on Stage 1b side +conditions). NOT a wrapper. + +|`+formal/L4.v+` |(Definitions only) |0 |✅ active — L4 labelling +discipline (`+PModeLinear+` / `+PModeAffine+` / `+PModeBoundaryMix+` + +`+program_mode_to_modality+` round-trip). No theorems. Phase A scaffold +landed 2026-05-28. + +|`+src/abi/Ephapax/…+` (Idris2) |n/a |(E1–E6 + compileOk = 7 +`+0+`-quantity postulates) |✅ active — ABI; postulates are explicit +OWED-to-Coq forwards. See §1 Idris row below. + +|`+src/formal/Ephapax/…+` (Idris2) |working |none |✅ active — Region +linearity, narrow no-escape proof; no `+believe_me+` / `+sorry+` / +`+assert_total+` +|=== + +==== Seam audit (current 2026-06-01): every admit/axiom classified + +The current `+admit.+` / `+Admitted.+` set in `+formal/*.v+`. The +remaining admits are exactly the *pre-existing L1 structural debt* (with +one true parallel mirror that closes when its original closes) plus the +*sacrosanct legacy preservation* (provably false per +`+Counterexample.v+`). + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Location |Class |Closes when +|`+Semantics_L1.v:576+` (`+admit.+`) |*Pre-existing* L1 structural — +`+region_shrink_preserves_typing_l1_gen_m+` / T_Region_Active_L1 +shadowed sub-case; list-vs-multiset gap |L1 perm/multiset bridge OR +`+T_Region_*_L1+` redesign (deferred to Phase D) + +|`+Semantics_L1.v:646+` (`+admit.+`) |*Parallel mirror* of `+:576+` — +T_Region_Active_L1_Echo shadowed sub-case; structurally identical |Same +as `+:576+` (mechanical replay) + +|`+Semantics_L1.v:678+` (`+Admitted.+`) |*Outer marker* — depends on +internal `+:576+` + `+:646+` |When both internal admits close + +|`+Semantics_L1.v:1994+` (`+admit.+`) |*Pre-existing* L1 structural — +`+region_liveness_at_split_l1_gen+` / T_Region_Active_L1 `+binder = rv+` +sub-case; *GENUINELY FALSE as stated* (single-case counterexample +documented at site lines 1923-1926); current `+Admitted.+` is a +transparency mark, not "`almost done`" |L2 effect-typed `+TFun+` per +`+PRESERVATION-DESIGN.md §5.1+`; closure requires reformulation, not +direct proof + +|`+Semantics_L1.v:2014+` (`+admit.+`) |*Parallel mirror* of `+:1994+` — +T_Region_Active_L1_Echo `+binder = rv+` sub-case; inherits same falsity +|Same as `+:1994+` + +|`+Semantics_L1.v:2028+` (`+Admitted.+`) |*Outer marker* — depends on +internal `+:1994+` + `+:2014+` |When both internal admits close (i.e., +never directly — only via reformulation) + +|`+Semantics_L1.v:3132+` (`+admit.+`) |*Pre-existing* — +`+preservation_l1+` body, covering S_StringConcat_Step2 + S_App_Step2 + +S_Pair_Step2 cases; lambda-rigidity gap per +`+PRESERVATION-DESIGN.md §4.8+` |Phase 3b Stages 2/3/4 (#240/#241/#242) +— L2 effect-typed lambdas with `+R_in/R_out+` syntactic annotations + +|`+Semantics_L1.v:3133+` (`+Admitted.+`) |*Outer marker* — depends on +internal `+:3132+` |When `+:3132+` closes + +|`+Semantics.v:9257+` (`+Admitted.+`) |*🛑 Sacrosanct* — legacy +`+Theorem preservation+`, *provably false* per `+Counterexample.v+` +(owner directive 2026-05-27) |Never. The `+Admitted.+` is correct. +|=== + +*Falsity audit* — two admits are not "`unproven but true`"; they are +*provably false as stated*: + +[arabic] +. `+Semantics.v:9257+` — refuted by `+Counterexample.v+`; sacrosanct. +. `+Semantics_L1.v:1994+` (and its mirror `+:2014+`) — refuted by +single-case counterexample `+ERegion rv (EI32 5)+` at `+R=[rv]+` +documented in the source at lines 1923-1926. Their `+Admitted.+` is a +transparency mark acknowledging the residual sub-case is false. Closure +requires reformulation at the L2 layer (lambda-rigidity gap), not a +direct proof. + +No `+Axiom+` declarations in `+formal/*.v+`. `+Counterexample.v+` / +`+Counterexample_L2.v+` / `+Counterexample_L2_nested.v+` carry 5 + 5 + 5 +Qed respectively; `+TypingL1.v+` / `+Modality.v+` / `+Echo.v+` / +`+TypingL2.v+` are all `+admit.+`-free. + +==== Idris2 side (proof carriers, not Coq mechanisation) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Concern |File(s) |Status +|Region linearity |`+src/formal/Ephapax/Formal/RegionLinear.idr+` +|working — explicitly "`REAL proof — not believe_me, not assert_total`" + +|ABI surface (17 files) |`+src/abi/Ephapax/…+` |working — clean of +`+believe_me+` / `+sorry+` +|=== + +==== Tools + +* *Coq 8.18+* for `+formal/*.v+` — primary mechanisation. +* *Idris2 0.8.0* (needs `+IDRIS2_PREFIX+` pointing at a prefix with +TTCs) for `+src/abi/Ephapax/…+`. +* *Just* as the build orchestrator (`+just proofs+`, +`+just idris-build+`, `+just golden+`). + +==== Build oracles + +* `+coqc 8.18.0+` is the *only authoritative answer* to "`is this +proved?`" for `+.v+` files. Comments in source claiming `+Qed+` without +`+coqc+` acceptance are not evidence. +* `+idris2 --check+` is the same for `+.idr+` files. +* Both are wired into `+rust-ci.yml+`’s "`Coq proofs`" and "`Idris2 +build`" jobs. + +''''' + +=== §5. Closure plan + current-branch state + foundation audit (2026-06-16) + +____ +This section is a dated *status + plan* snapshot. It does *not* +supersede §1–§4 (which track `+main+`); it records (a) the authoritative +ground-truthed admit set on the current proof branch, (b) the committed +closure route, and (c) the independent trust audit of the foundation +repos the route builds on. Sourced from a ground-truth sweep +(`+coqc+`/`+grep+`-verified, not doc-trusted), not from prose. + +*Companion design docs* `+L1-REGION-REFOUNDATION-PLAN.md+` and +`+L1-ELIMINATOR-FORK.md+` (referenced below) live in *PR #299* (branch +`+docs/l1-closure-research+`), pending merge to `+formal/+`. The +tropical applicability case study is `+tropical-resource-typing+` PR #21 +(`+docs/applications/ephapax-l1-regions.adoc+`). +____ + +==== 5.1 Current proof branch + +Branch `+proofs/l1-effect-typing-Aprime+` (A′ effect-typed-only +lambdas). *A′ closes 0 of 4 L1 admits* — the open obligations are +region/eliminator cases, not lambda-rigidity cases — so its +adoption-vs-revert disposition is still open (it is groundwork for the +choreographic `+S_App_Step2+` case, not a closure). + +All four L1 lemmas (`+region_shrink+`, `+region_liveness+`, +`+step_pop+`, `+preservation_l1+`) each carry an outer `+Admitted.+` on +*both* this branch *and `+main+`* (`+step_pop_disjoint_from_type_l1+` is +present on `+main+` too, at `+Semantics_L1.v:3178+`/`+:3433+`). So +`+formal/+` holds *5 outer `+Admitted.+`* (1 legacy + 4 L1) on main — +*but the §4 `+status-gate+` marker reads `+4+` and the §4 prose says "`3 +outer `+Admitted.+` in `+Semantics_L1.v+``": both are stale by one* (the +true `+Semantics_L1.v+` count is 4 outer markers, → 5 with the legacy). +This is a real *`+main+`-wide* drift, not branch-local — and exactly the +drift the *broken + unwired* `+status-gate+` (see §5.7) was supposed to +catch but couldn’t. §4’s marker is corrected to `+5+` in this PR; the +companion `+fix/proof-gates+` PR repairs the script (`+//+`→`+#+`) and +wires it into CI so future drift is flagged. (A′ shifts the line numbers +but not the count.) + +==== 5.2 The four L1 admits — authoritative classification (this branch) + +[width="99%",cols="20%,16%,16%,16%,16%,16%",options="header",] +|=== +|# |Lemma |Site (this branch) |Internal admits |Class |Closes via +|1 |`+region_shrink_preserves_typing_l1_gen_m+` |`+Semantics_L1.v:441+` +|`+:572+`, `+:642+` |*live structural* (list-vs-multiset tear) +|*Dissolves* — carrier refactor `+region_env : list+` → count-map. +`+L1-REGION-REFOUNDATION-PLAN.md+` + +|2 |`+region_liveness_at_split_l1_gen+` |`+Semantics_L1.v:1904+` +|`+:1956+`, `+:1976+` |*provably FALSE* as written (witness +`+ERegion rv (EI32 5)+` at `+R=[rv]+`) |*Repaired* — restate as graded +`+live R rv ∧ no-exit-of-rv ⇒ live R' rv+`; prove from +`+count_occ_le_l1_m+` monotonicity + ~13-site call audit. +`+L1-REGION-REFOUNDATION-PLAN.md §5+` + +|3 |`+step_pop_disjoint_from_type_l1+` |`+Semantics_L1.v+` |*1* (reduced +from ~9–11 in 2026 via the `+step_R_change_shape+` rewrite — see +`+L1-ELIMINATOR-FORK.md+` §8). The single residual obligation is +`+step exits r0 ∧ r0 ∈ free_regions(T) ⟹ cnt r0 R ≥ 2+`; its direct-exit +sub-case is vacuous, the residue is congruence-exit count-coherence. +Diagnosis corrected: *not* §4.8 lambda-rigidity (the witness has no +lambda) but region-count coherence at a region exit. *⚠️ 2026-07-07 +(experiment 3): provably FALSE AS STATED* — +`+formal/Counterexample_StepPop.v+` (Qed, zero axioms) exhibits +`+EPair (ERegion rv (EI32 5)) (EVar 0)+` at `+R=[rv]+`: it types +(`+T_Var_Lin_L1+` has no `+In rv R+` premise), steps (`+S_Region_Exit+` +under `+S_Pair_Step1+`), and lands at `+R'=[]+` with +`+rv ∈ free_regions(T)+`. Same class as admit #2’s history: the +`+Admitted.+` hides a falsehood, not a gap. |*the eliminator fork* — now +with a Qed forcing argument that typing ⇏ trace coherence +|*Reformulation, owner-gated* — restate with a liveness/coherence +premise or restrict to value-anchored consumers (the sole caller retypes +an `+ELoc+` value whose `+T_Loc_L1+` typing carried `+In r R+`; the +refuting sibling is a variable, which no `+S_*_Step2+` case retypes at +`+R'+`). The choreographic route’s task is now provably "`make coherence +a judgment-level input`", not "`derive it`". +`+Counterexample_StepPop.v+` header + `+L1-ELIMINATOR-FORK.md+` §3–§8 + +|4 |`+preservation_l1+` (capstone) |`+Semantics_L1.v:3367+` |`+:3379+` +|*⚠️ provably FALSE AS STATED (2026-07-07, experiment 3b)* — +`+formal/Counterexample_PreservationL1.v+` (Qed, zero axioms): +`+EStringConcat (ELoc 0 rv) (ESnd (EPair (ERegion rv (EI32 0)) (EVar 0)))+` +at `+R=[rv]+` types in both modalities, steps (the sibling’s region-exit +pops the only `+rv+`), and the post-state is untypable — the `+ELoc+` +anchor’s `+T_Loc_L1+` demands `+In rv []+`. The value-anchored escape +hatch suggested by `+Counterexample_StepPop.v+` is closed: the dangling +dependence here IS a value. |*NOT closable by lemmas* — the judgment +must carry the temporal invariant (region-liveness-through-reduction): +either coherence as a judgment input (choreographic-foundational route, +now Qed-mandatory) or leaf-rule strengthening that rejects the witness +at typing (A′/path-3 family, to be re-examined against this witness). +Owner decision; record in `+PRESERVATION-DESIGN.md+`. +|=== + +`+preservation_l3+` (`+Semantics_L1.v+`, *Qed*) is real and depends on +*only* admit #1 (`+region_shrink+`); it flips *unconditional* the moment +#1 closes. `+L4.v+` is *scaffold-unbuilt* (`+ProgramMode+` + round-trip +mapping; definitions only, no theorems — not debt, just not yet built). +No hidden 5th admit, no `+Axiom+`/`+Parameter+`/`+Hypothesis+` smuggled +assumption, no fake-`+Qed+` was found anywhere in `+formal/*.v+`. + +==== 5.3 Idris2 ABI proof needs (a separate axis from the Coq L1 admits) + +`+src/abi/Ephapax/ABI/Invariants.idr+` states six compiler-correctness +invariants as *erased (0-quantity) postulates*, each an explicit _owed_ +forward to a discharging proof: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|ID |Invariant |Status +|E1 |Type preservation (operational) |*owed* — forwards to Coq +`+preservation+` (legacy is false; real obligation is +`+preservation_l1+`) + +|E2 |Linear consumption across control flow |E2a position-form +*DISCHARGED* (`+splitLinearCoverage+`, Qed, `+Formal/Qualifier.idr+`); +E2b control-flow form *owed* + +|E3 |No region escape |static/type-level *DISCHARGED* +(`+noEscapeTheorem+`, Qed, `+Formal/RegionLinear.idr+`); operational +form *owed* + +|E4 |No runtime GC |*owed + flagged* — `+noGCExtract+` is a +*tautological/vacuous wrapper* (returns inputs unchanged), not a proof + +|E5 |WASM compilation correctness |*owed — no formalisation exists* +(Rust backend only). Highest-leverage compiler-correctness gap; enables +`+compileOkImpliesWasmTyped+` in `+Foreign.idr+` + +|E6 |IR (S-expr) lowering correctness |*owed — no formalisation* +|=== + +Real Idris Qed proofs verified clean (no +`+believe_me+`/`+assert_total+`/holes): `+splitLinearCoverage+`, +`+noEscapeTheorem+`, `+orthogonalityLemma+`, `+compileResultRoundTrip+`, +`+outlivesTransitive+`. Both `+abi-verify.yml+` and `+coq-build.yml+` +are hard CI gates (`+idris2 --build+`; `+coq_makefile+` + +`+Print Assumptions+` on the Phase-D top-level results). + +==== 5.4 Committed closure route (owner-committed 2026-06-16) + +[arabic] +. *Clean win (deterministic):* tropical carrier refactor → dissolves #1, +repairs the false #2, then flips `+preservation_l3+` unconditional. *Net +4 → 2 admits, 0 false lemmas.* (`+L1-REGION-REFOUNDATION-PLAN.md+`.) +. *Deciding experiment (cheap, do before the general theory):* the +minimal `+EDrop (EVar j : TString rv)+` choreography over two segments — +does subject reduction carry liveness through `+S_Region_Exit rv+` +coherently, or relocate it into a projection-coherence side-condition? +(`+L1-ELIMINATOR-FORK.md §6+`.) *DONE* +(`+formal/L1ChoreoExperiment.v+`): verdict *relocates* — and it +sharpened _why_ (the naive model collapses projection onto the scalar +predicate, which is false at re-entry; the real fork lives in the +*congruence* cases, not the clean `+ERegion+` exit). 2b. *Congruence +experiment (non-collapsed model):* *DONE* +(`+formal/L1ChoreoExperiment2.v+`, `+L1-ELIMINATOR-FORK.md §9+`). A +trace model that keeps subterm-relative *order* (not just the scalar +balance) *closes — in the model* — the obligation `+step_pop+` relocates +to: `+sibling_use_keeps_region_live+` is `+Qed+`, axiom-free, +non-circular, and unprovable from the snapshot alone. The congruence +verdict is upgraded from "`relocates`" to "`closes in the trace model, +reduced to **one wiring lemma**`" (`+wiring_obligation+`: typing ⇒ +`+valid+` trace at `+k = cnt rv R+`). Experiment 1’s re-entry failure +mode provably does not recur. *Closes no admit yet* (honest count still +3); it is the consolidation step that names the precise next target. +. *If green → choreographic-foundational re-derivation:* region liveness +as a tropically-graded choreography across time segments (regions = +session- typed resources, scopes = segments, preservation = subject +reduction). Closes #3 → *2 → 0*, and likely gives *L4* its dyadic +(2-party choreography) structure for free — one foundation, all four +layers. *Next concrete step:* discharge `+wiring_obligation+` (plausibly +a structural induction on the typing derivation — each trace +`+Close+`/`+Use+` `+>= 1+` premise mirrors a local `+In r R+` premise in +`+T_Region_Active_L1+` / `+T_Loc_L1+`). + +==== 5.5 Foundation trust audit (2026-06-16) — GO + +The route reuses proofs from three sibling repos. An independent +ground-truth audit (cold rebuilds + kernel/flag probes, not doc-trust) +cleared all three — the owner’s "`did an LLM let a blank/sorry +through?`" concern *did not materialise*: + +* *`+epistemic-types+`* (Agda 2.8.0) — *trustworthy/GO.* +`+agda --no-libraries … All.agda+` exit 0; `+-W error+` still exit 0; +`+--safe+` _proven enforced_ (postulate-injection probe rejected, exit +42); zero escape hatches; zero orphans. Caveat: its `+EchoBridge+` is a +deliberate scaffold (grade plumbing, no proven graded-loss laws). +* *`+tropical-resource-typing+`* (Lean 4.13.0) — *trustworthy/GO +(strongest).* Cold `+lake build+` exit 0 ×2; kernel `+#print axioms+` on +all 17 load-bearing results → only `+propext+`/`+Quot.sound+` or none +(*no `+sorryAx+`, no `+Classical.choice+`*). Every lemma the route +reuses is non-vacuous. +* *`+echo-types+`* (Agda 2.8.0) — *trustworthy/GO, build on the WIRED +layer only.* 164 wired files (`+--safe --without-K+`, exit 0, zero +postulates) vs 15 orphaned (~8%). Three caveats: (a) the *one* real +postulate lives in the orphaned, exempted exploratory +`+EchoImageFactorizationPropPostulated.agda+` (honest quarantine, +imported by nothing); (b) the experimental `+echo-additive+` *variance* +suite is *orphaned + RETRACTED (R-2026-05-18)* and `+VarianceGate.agda+` +self-declares "`NO proven theorems`" — *do not cite variance as proven*; +(c) two dangling `+libraries+` configs cause false "`name not found`" +failures — fix before handing off. + +==== 5.7 Gate integrity — threatened (surfaced 2026-06-16, NOT yet fixed) + +Two CI-gate findings threaten the "`is it proved?`" guarantee and are +recorded here so they are not lost (no fix applied — surfaced for +decision): + +* *`+coq-build-gate+` is implemented but NOT a _required_ status check.* +`+.github/settings.yml:113-118+` requires only `+hypatia-scan+` + +`+codeql+`; the always-on `+coq-build-gate+` aggregator (PR #254) is not +in `+required_status_checks+`. *Consequence: a new +`+Admitted.+`/`+Axiom+`, or a proof that fails `+coqc+`, could merge to +`+main+` undetected.* This is the single highest-priority gate fix. +* *`+scripts/status-gate.sh+` is doubly dead:* (a) line 3 is a `+//+` +C-style comment (should be `+#+`) → the script is syntactically invalid +and aborts immediately (a delimiter bug, fixable without touching the +owner string); +[loweralpha, start=2] +. it is wired into no CI workflow (only `+just status-gate+`). So the +"`Coq admitted proofs remaining: N`" drift check in §4 never runs in CI +— which is also why the §5.1 count drift (4 vs 5 on this branch) was +never flagged automatically. + +(Lower-severity: `+scorecard-enforcer+` defaults the parsed score to +`+0+` on a malformed SARIF → possible false-fail; `+boj-build.yml+` +`+continue-on-error+` is acceptable for an external service.) + +==== 5.6 Bottom line + +ephapax’s remaining proof work = the 4 L1 admits (→ 2 by the +deterministic clean win, → 0 if the eliminator-fork experiment goes +green) + the unbuilt L4 + the owed Idris ABI obligations (E1, E2b, +E3-operational, E4, E5, E6, with E5 the highest leverage). The legacy +`+Semantics.v preservation+` is *not debt* — it is a deliberate, +witnessed false result. The plan is staged, ground-truthed, and the +foundation it builds on is independently verified. + +''''' + +=== Cross-references + +* link:STATUS.adoc[`+STATUS.adoc+`] — past / present / future temporal +map. +* link:formal/PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md+`] +— canonical four-layer design doctrine. +* link:CLAUDE.md[`+CLAUDE.md+`] — agent guidance; owner directive +2026-05-27. +* link:formal/Counterexample.v[`+formal/Counterexample.v+`] — the 5 Qed +lemmas that pin the soundness gap. +* https://github.com/hyperpolymath/echo-types[`+https://github.com/hyperpolymath/echo-types+`] +— upstream Agda foundation for L3. +* https://github.com/hyperpolymath/nextgen-languages/blob/main/docs/disambiguation/ephapax-vs-affinescript.md[Disambiguation: +ephapax vs AffineScript] — canonical side-by-side table. diff --git a/PROOF-NEEDS.md b/PROOF-NEEDS.md deleted file mode 100644 index a8787d61..00000000 --- a/PROOF-NEEDS.md +++ /dev/null @@ -1,523 +0,0 @@ - - - - -# Proof needs — Ephapax (linear + affine sublanguages) - -> ## 🛑 First-read disambiguation (human + machine readers) -> -> **This document is for `hyperpolymath/ephapax`.** Ephapax is a research -> language for compile-time WebAssembly memory safety, formally verified -> in Coq + Idris2. -> -> **Ephapax is NOT `hyperpolymath/affinescript`.** AffineScript is a -> separate, unrelated language (JS/TS/AffineScript successor, OCaml + AffineScript -> runtime, no mechanised proofs, no first-class regions). The two share -> only the compile target (`hyperpolymath/typed-wasm`). -> -> **`ephapax-affine` (in this document) is the affine *sublanguage* of -> Ephapax** — the versatile prototyping companion to `ephapax-linear`. -> It lives inside this repo (`ephapax-linear/` crate). **It is NOT -> AffineScript.** The lexical overlap of the word `affine` is a -> coincidence of substructural-logic terminology, not a project -> relationship. Do not apply lessons or tactics across the boundary. -> -> If you found this document by searching for "affine type system", -> verify which language you actually need before proceeding. Canonical -> side-by-side table: -> https://github.com/hyperpolymath/nextgen-languages/blob/main/docs/disambiguation/ephapax-vs-affinescript.md - -## Reading guide - -This file is the *proof-debt audit* for Ephapax post-counterexample -(2026-05-26). It supersedes earlier versions of `PROOF-NEEDS.md` that -still referenced the legacy preservation closure plan as if it were -viable. - -The audit is split by sublanguage: - -* **§1** — what's done (mechanised, Qed) for each sublanguage. -* **§2** — what's todo (active work, by layer). -* **§3** — what's banned (explicit do-not-do anti-patterns). -* **§4** — counts and file-by-file map. - -For the architectural background see -[`STATUS.adoc`](STATUS.adoc) and -[`formal/PRESERVATION-DESIGN.md`](formal/PRESERVATION-DESIGN.md). - ---- - -## §1. What's done - -### ephapax-linear (strict / obligation-preserving sublanguage) - -| Component | File | Status | -|---|---|---| -| L1 judgment (`has_type_l1`) | `formal/TypingL1.v` | 100% — 0 admits, 2 Qed | -| L1 judgment indexed by modality `m : Modality` | `formal/TypingL1.v` | landed via PRs #176 + #177 | -| L2 modality core (`Modality.v`, `linear_to_affine`) | `formal/Modality.v` | 1 Qed, zero axioms | -| L3 calculus (echo / residue fiber + degrade + no-section proof) | `formal/Echo.v` | 12 Qed, 0 admits | -| Linear-mode forward progress lemmas | `formal/Semantics_L1.v` | 37 Qed; **2** outer `Admitted.` markers (`step_pop_disjoint_from_type_l1` / `preservation_l1`) cover **2** internal `admit.` cases at current lines 3315/3336 (rebuilt `coqc 8.18.0` ground truth 2026-06-27; the earlier 5-internal / lines-576/646/1994/2014/3132 figure is pre-dissolution, superseded by the §4 marker = 3 total). Phase 3b Stage 1a (#252, MERGED 2026-05-30: `tfuneff_lambda_free` + `Counterexample_L2_nested.v`) and Stage 1b (#253, MERGED 2026-05-31: closed-value substitution + `preservation_l2_app_eff_beta` β-case for TFunEff). Slice 4 L3 wiring delivered `preservation_l3_region_active_echo` / `preservation_l3_drop_echo` / `preservation_l3` umbrella. | -| L2 β-case lemmas (Stage 1b) | `formal/TypingL2.v` | 10 Qed total: `weaken_modality` family (5), `preservation_l2_via_l1` (conditional on `preservation_l1`), `linear_value_retype_l1_m`, and 3 `preservation_l2_app_eff_beta_*` variants (Linear / ground_nonlinear / tfuneff conditional on Stage 1b side conditions P1+P2+P3). Stage 2/3/4 (#240/#241/#242) drop the conditions. | -| L4 labelling scaffold (Phase A, 2026-05-28) | `formal/L4.v` | `ProgramMode` enum (`PModeLinear` / `PModeAffine` / `PModeBoundaryMix`) + `program_mode_to_modality` round-trip. Definitions only — no theorems, zero axioms. | -| Counterexample_L2 regression witnesses (Phase 4c + 3b) | `formal/Counterexample_L2.v`, `formal/Counterexample_L2_nested.v` | 5 + 5 Qed pinning the Phase 4c (fresh-region scope crossing) and Phase 3b (nested TFunEff) soundness-gap classes | -| Counterexample regression witness | `formal/Counterexample.v` | 5 Qed (`bad_input_untypable_l1` proved under both modes) | -| Operational checker (Rust, ephapax-linear sublanguage) | `ephapax-linear/src/linear.rs` | working — discharges resource-exact obligation | - -### ephapax-affine (relaxed / degradable sublanguage) - -| Component | File | Status | -|---|---|---| -| Affine-mode judgment indexing | `formal/TypingL1.v` (`m = Affine` branch) | landed; 3 Affine-only rules `T_Lam_L1_Affine`, `T_Case_L1_Affine`, `T_If_L1_Affine` | -| Linear ⇒ Affine weakening | `formal/TypingL1.v` `linear_to_affine` | Qed, zero axioms | -| Operational checker (Rust, ephapax-affine sublanguage) | `ephapax-linear/src/affine.rs` | working — permits weakening / graceful abandonment | -| Affine-mode echo discipline (LEcho Affine = lowered triple) | `formal/Echo.v` (calculus) | calculus done; rule wiring pending | -| 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; Phase 3b Stage 1a + 1b landed 2026-05-30/31 via #252 + #253. Remaining admits are pre-existing L1 structural debt + one provably-false-as-stated sub-case (`Semantics_L1.v:1994` / mirror `:2014` — closure requires Phase D reformulation, not direct proof). See §4 seam audit. | - -### Counterexample regression - -| Lemma | Status | What it shows | -|---|---|---| -| `bad_input_untypable_l1` | Qed | the configuration that steps-and-untypes is rejected at type check under the new L1 judgment, in both Linear and Affine modes | -| 4 supporting lemmas | Qed | the legacy judgment *types* the same configuration | - -→ The counterexample establishes the gap. The new L1 judgment closes it. - ---- - -## §2. What's todo - -### Near-term (L1 ⇒ L2 integration debt — mechanical) - -| Item | File | Estimate | -|---|---|---| -| ✅ 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 | -| ✅ Generalise typing_preserves_length_l1 to modality-polymorphic | `formal/Semantics_L1.v` | done 2026-05-27 | -| ✅ 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) | -| ✅ 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) | -| 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 | -| State and prove `preservation_l1` for both modes | `formal/Semantics_L1.v` | depends on region_shrink + region_liveness narrow admit | - -### Near-term (L3 wiring — design + mechanisation) - -L3 calculus is done in `formal/Echo.v` (12 Qed, no admits). What's missing -is the *integration* into the typing judgment and the step rules. -The shape is laid out in `formal/PRESERVATION-DESIGN.md §6.3` "Where echo -enters the typing rules" and (for the diagram) in §6 (to be added). - -| Item | What it does | Notes | -|---|---|---| -| ✅ 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) | -| ✅ 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) | -| ✅ 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) | -| ✅ 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). Initial +3 internal admits in [region_shrink_preserves_typing_l1_gen_m] + [region_liveness_at_split_l1_gen] were parallel-rule MIRRORS of pre-existing structural admits; the avoidable T_Region_L1_Echo mirror was CLOSED in slice 4 — only the two true T_Region_Active_*_L1_Echo shadowed-case mirrors remain, both blocked by the same pre-existing list-vs-multiset structural debt as the originals | -| ✅ 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 | -| ~~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. | -| ✅ State and prove `preservation_l3` | Per-layer preservation theorem against the L3 invariants for the new echo-emitting step rules + echo-typed paths | done 2026-05-27 (L3 wiring slice 4 — capstone). Two per-case Qed lemmas (`preservation_l3_region_active_echo` for `S_Region_Exit_Echo` × `T_Region_Active_L1_Echo`, and `preservation_l3_drop_echo` for `S_Drop_Echo` × `T_Drop_L1_Echo`) plus an umbrella `preservation_l3` (their conjunction, Qed). Zero new admits or axioms. Per-case alignment forced by `T_Echo_L1`'s witness-type premise; non-deterministic crossover cases are non-preserving by design (typing derivation pins the path). Conditionally Qed under the pre-existing `region_shrink_preserves_typing_l1_gen_m` L1 structural admit per PRESERVATION-DESIGN.md §5.1. | -| ⏸ Register `TEcho` as linear via `is_linear_ty` (Phase B Slice 1) — DEFERRED 2026-05-28 | Realises the design intent annotated at `TypingL1.v:T_Observe_L1`: under Linear discipline, unobserved `TEcho` must fail typing closure | **deferred until Phase D**. Investigation 2026-05-28: adding `| TEcho _ => true` to `is_linear_ty` is structurally non-vacuous at L1 — `EEcho T v` is both a value (`VEcho`) and typed at `TEcho T` (via `T_Echo_L1`, TypingL1.v:351-354), refuting the conclusion of `linear_value_is_loc_l1` (Semantics_L1.v:891) and breaking 9 call-sites in `subst_typing_gen_l1_m` (Semantics_L1.v:1326-1605, Qed). Realising the wire requires a disjunctive rewrite of `linear_value_is_loc_l1` plus per-caller handling of the TEcho disjunct — substantially larger than the original "1-line definitional" framing. Owner deferred to after Phase D (L2 effect-typed TFun) when the L1 substitution chain reshapes naturally. The legacy `Semantics.v` branch is vacuous (no rule produces `TEcho`) and would have closed trivially. | -| ⏸ Close the L1 list-vs-multiset structural admits at `Semantics_L1.v:553/621` (Phase C) — DEFERRED 2026-05-28 | The `T_Region_Active_L1`-shadowed sub-case of `region_shrink_preserves_typing_l1_gen_m` requires bridging body derivations across input shrinkage when the outer region matches a body-internal active occurrence | **deferred until Phase D**. Four design paths surfaced + analytically evaluated 2026-05-28 (recorded in this session's memory + companion design note `Semantics_L1.v:366-402`): (a) Permutation-based perm-l1 leaves a list-structure output mismatch (concrete counterexample at `R_body = [a; rr; b; c]` vs `R_body' = [b; rr; a; c]`); (b) multiset reformulation of `remove_first_L1` cascades through every L1 rule's output threading; (c) `T_Region_*_L1` rule redesign (Permutation premise on body output) was implemented in-flight on `proof/phase-c-l1-multiset-bridge` — closes sub-sub-case (ii) (R has exactly 1 rr via count-vacuity) but leaves sub-sub-case (i) (R has ≥2 rr) requiring body-input-shrinkage, which itself is NOT a theorem (T_Loc_L1 counterexample at count=1; T_Let composition fails for the count-≥-2 precondition); (d) defer was selected as the most mathematically safe + elegant path. Phase D (L2 effect-typed TFun) introduces the natural setting: lambda bodies' R-flow becomes effect-typed, which gives the structural invariant that lets body-input-shrinkage discharge. The 2 admits at lines 553/621 remain as documented L1 debt, and the outer `Admitted.` at line 653 + the `preservation_l3` dependency annotation in PRESERVATION-DESIGN.md §5.1 continue to attribute them correctly. | - -### Mid-term (L4 — scaffold landed 2026-05-28) - -L4 covers dyadic interaction semantics (mother–child distinction). -Per `formal/PRESERVATION-DESIGN.md §7`, L4 is "not a separate proof -layer. It is a labelling discipline at the program / module level... -No proofs change." - -Phase A scaffold landed 2026-05-28: - -| Item | File | Status | -|---|---|---| -| ✅ L4 design page | `formal/L4-DYADIC.md` | done — extracts + extends design doc §7 | -| ✅ Labelling enum `ProgramMode` + `program_mode_to_modality` mapping to L2 | `formal/L4.v` | done — Definitions only, no theorems, no admit, no axiom | - -Future L4 work (post-Phase D, optional): surface-syntax wiring in -the borrow checker so `#![ephapax_linear]` / `#![ephapax_affine]` / -`#![module_boundary_mix]` parse and select the L2 modality. This is -implementation, not proof debt. - -### Value-prop directions (committed and speculative) - -| Direction | Status | Proof obligation | -|---|---|---| -| Linear Echo + Region-Based Memory Management (S_Region_Exit certified-evidence emission) | Committed for v1 | `preservation_l3` for the Region branch | -| Selective reversibility via L2 modality (Linear ≤ Affine thin-poset) | Committed for v1 | Already mechanised in `Modality.v` | -| Debugging / provenance via Linear Echoes as type-system receipts | Committed for v1 | Follows from L3 wiring | -| Linear discipline on deterministic drop (Rust-style) for critical resources | Speculative | Compatible with L3 wiring; not committed v1 | -| GDPR-style certified erasure as compliance use-case | Direction | Concrete instantiation; no separate proof obligation | - ---- - -## §3. What's NOT to do (banned anti-patterns) - -These are not preferences. They are explicit, durable, owner-issued -2026-05-27 directives. - -### A. Do not close the legacy preservation theorem - -* ❌ `Theorem preservation` in `formal/Semantics.v` is **provably false**. - No proof closes it. The `Admitted.` is correct. Leave it. -* ❌ Do not add lemmas to `formal/Semantics.v` aimed at that closure. - Examples that have wasted real session time: - * Lemma B variants (`step_preserves_type`, - `step_output_context_eq`, `step_preserves_type_at_pre`, - `step_output_context_eq_at_pre`) - * `region_add_typing_for_non_values` - * Strengthened substitution lemmas - * `sibling_typing_transfers_across_step`-style helpers -* ❌ Do not patch the legacy `formal/Typing.v` judgment. - `Counterexample.v` depends on its falsity. - -### B. Do not close L1/L2 admits by ad-hoc cross-layer reasoning - -* ❌ `region_liveness_at_split_l1` (if it returns) must NOT be closed - by adding proof tricks, side conditions discharged ad-hoc, or - strengthened lemma signatures within the current judgment shape. - Post-L2-hybrid these closures are cross-layer; the layered design - intentionally splits some closures across layers. -* ❌ The 9 Semantics_L1.v admits are L2-integration debt (bullet - structure for new Affine constructors). They are *not* invitations - to introduce new `Axiom` declarations. - -### C. Do not bake linearity into Echo Types - -Echo semantics (L3) and structural discipline (L2) must remain -orthogonal and compositional. - -* ❌ Do not write `LinearEcho` and `AffineEcho` as separate type - formers. There is **one** type former (the fiber); the two modes - are *applications* of it with different witness shapes. -* ❌ Do not pattern-match on `Linear`/`Affine` inside `Echo.v`. - The discipline is read from L2's `m : Modality` at the typing-rule - boundary, not inside the calculus. - -### D. Do not treat Echo Types as a tracing-GC replacement - -* ❌ Echo Types do not solve reachability or cycles. -* ❌ Echo Types are not a "fire-and-forget" automatic memory manager. -* ✅ Echo Types are a *type-theoretic discipline for accountability of - irreversible reclamation* — strongest in conjunction with RBMM - (regions) or deterministic ownership (Rust-style drop). - -### E. Do not follow pre-2026-05-26 plans - -* ❌ Anything dated before 2026-05-26 that proposes closing - `preservation` to `Qed.` is pre-discovery. PRs #92, #102, #104, - #106, #114, #116, #117, #121, #146 are archaeology. -* ❌ Branch `lemma-b-phase2-middle-narrow` (deleted post-archaeology - cherry-pick) was the most recent pre-discovery attempt. Do not - resurrect. - -### F. Do not conflate `ephapax-affine` with AffineScript - -* ❌ `ephapax-affine` lives in this repo's `ephapax-linear/` crate - alongside `ephapax-linear`. It is a *sublanguage* of Ephapax. -* ❌ `AffineScript` (`hyperpolymath/affinescript`) is a different - language with no source-level overlap. Borrow-checker tactics from - `affinescript/lib/borrow.ml` do not apply here, and vice versa. -* ❌ The lexical overlap of the word `affine` is a coincidence. - Substructural-logic family terminology, not project relationship. - -### Anti-pattern detector - -If your session is producing any of the following, **stop and escalate -to the owner**: - -* `sibling-region-disjointness` side conditions on compound rules -* region-weakening predicates indexed on syntactic shape -* admit-shuffling between `Semantics.v` and a new lemma file -* "the previous-PR-line-of-attack just needs one more lemma" -* proposing to close `Theorem preservation` in `Semantics.v` to `Qed.` - by any chain of reasoning -* adding new `Axiom` declarations to discharge L1/L2 gaps -* writing a `LinearEcho` distinct from an `AffineEcho` -* applying tactics or framings from `hyperpolymath/affinescript` here - ---- - -## §4. Counts + file-by-file map - - -Coq admitted proofs remaining: 3 - -(1 outer `Admitted.` in `formal/Semantics.v` — sacrosanct legacy -preservation, provably false per `Counterexample.v` + **2** outer -`Admitted.` markers in `formal/Semantics_L1.v` — the two open L1 lemmas -`step_pop` / `preservation_l1`. -**Updated 2026-06-26 (this branch) against rebuilt `coqc 8.18.0` + -`scripts/status-gate.sh --proofs` ground truth. Two closures dropped the -count 5 → 3:** -**(a) `region_shrink_preserves_typing_l1_gen_m` was *dissolved* (the -2026-06-16 multiset-perm closure) — its general form is proven false and -pinned in `formal/Counterexample_RegionShrink.v`, the surviving value -corollary `region_shrink_value` is `Qed`/axiom-free, and `preservation_l3` -is consequently UNCONDITIONALLY axiom-free.** -**(b) `region_liveness_at_split_l1_gen` — the provably-false -`In rv R -> In rv R'` lemma — was *removed*. Its 13 consumers in -`subst_typing_gen_l1_m` now route through the TRUE, `Qed` -`region_liveness_no_exit_l1_gen`, supplied with the honest -`val_region_no_exit` premise threaded through `subst_typing_gen_l1_m` / -`subst_typing_gen_l1` / `subst_preserves_typing_l1` and the L2 β-case -lemmas (`preservation_l2_app_eff_beta_linear` / `_l1`) — all of which -`Print Assumptions` now reports as "Closed under the global context" -(axiom-free) instead of depending on a false axiom.** -The two remaining outer markers are `step_pop_disjoint_from_type_l1` and -`preservation_l1` (capstone, gated on `step_pop`); §5's line numbers are -earlier-branch snapshots, superseded here.) - -### Per-file Qed / Admitted summary (as of 2026-06-01) - -| File | Qed | Admitted | Disposition | -|---|---:|---:|---| -| `formal/Semantics.v` (legacy) | n/a | **1** | 🛑 archaeology — single `Admitted.` at line 9257 (`Theorem preservation`, provably false); do not extend | -| `formal/Typing.v` (legacy) | n/a | 0 | 🛑 archaeology — `Counterexample.v` depends on falsity | -| `formal/Counterexample.v` | **5** | 0 | ✅ pinned regression witness (`bad_typable`, `bad_step`, `bad_post_untypable`, `t_loc_l1_R_preserving`, `bad_input_untypable_l1`) | -| `formal/Counterexample_L2.v` | **5** | 0 | ✅ Phase 4c soundness-gap witness — fresh-region scope crossing (`v_typed_at_empty`, `outer_typed`, `e_before_typed`, `e_step`, `e_after_untypable`) | -| `formal/Counterexample_L2_nested.v` | **5** | 0 | ✅ Phase 3b soundness-gap witness — nested TFunEff (analogue structure to `Counterexample_L2.v`) | -| `formal/TypingL1.v` | **2** | 0 | ✅ active — L1 judgment, modality-indexed | -| `formal/Semantics_L1.v` | **54** | **2** | ✅ active — Phase 3b Stage 1a + 1b landed via PRs #252 + #253. **2** outer `Admitted.` markers — the two open L1 lemmas (`step_pop_disjoint_from_type_l1` / `preservation_l1`). **Updated 2026-06-26: `region_shrink…gen_m` dissolved (2026-06-16) and `region_liveness_at_split_l1_gen` removed (this branch) via the honest `val_region_no_exit` reformulation — see the §4 marker note. `preservation_l3` and the L2 β-case lemmas are now unconditionally axiom-free. Counts are rebuilt `coqc 8.18.0` ground truth on this branch.** | -| `formal/Modality.v` | **1** | 0 | ✅ active — L2 core, zero axioms (`linear_to_affine`) | -| `formal/Echo.v` | **12** | 0 | ✅ active — L3 calculus mechanised | -| `formal/TypingL2.v` | **10** | 0 | ✅ active — `weaken_modality` (+ Affine_id + 3 `_le_*` variants), `preservation_l2_via_l1` (conditional on `preservation_l1`), `linear_value_retype_l1_m`, and 3 β-case lemmas (`preservation_l2_app_eff_beta_linear`, `_ground_nonlinear`, `_tfuneff` conditional on Stage 1b side conditions). NOT a wrapper. | -| `formal/L4.v` | (Definitions only) | 0 | ✅ active — L4 labelling discipline (`PModeLinear` / `PModeAffine` / `PModeBoundaryMix` + `program_mode_to_modality` round-trip). No theorems. Phase A scaffold landed 2026-05-28. | -| `src/abi/Ephapax/…` (Idris2) | n/a | (E1–E6 + compileOk = 7 `0`-quantity postulates) | ✅ active — ABI; postulates are explicit OWED-to-Coq forwards. See §1 Idris row below. | -| `src/formal/Ephapax/…` (Idris2) | working | none | ✅ active — Region linearity, narrow no-escape proof; no `believe_me` / `sorry` / `assert_total` | - -### Seam audit (current 2026-06-01): every admit/axiom classified - -The current `admit.` / `Admitted.` set in `formal/*.v`. The remaining -admits are exactly the **pre-existing L1 structural debt** (with one -true parallel mirror that closes when its original closes) plus the -**sacrosanct legacy preservation** (provably false per `Counterexample.v`). - -| Location | Class | Closes when | -|---|---|---| -| `Semantics_L1.v:576` (`admit.`) | **Pre-existing** L1 structural — `region_shrink_preserves_typing_l1_gen_m` / T_Region_Active_L1 shadowed sub-case; list-vs-multiset gap | L1 perm/multiset bridge OR `T_Region_*_L1` redesign (deferred to Phase D) | -| `Semantics_L1.v:646` (`admit.`) | **Parallel mirror** of `:576` — T_Region_Active_L1_Echo shadowed sub-case; structurally identical | Same as `:576` (mechanical replay) | -| `Semantics_L1.v:678` (`Admitted.`) | **Outer marker** — depends on internal `:576` + `:646` | When both internal admits close | -| `Semantics_L1.v:1994` (`admit.`) | **Pre-existing** L1 structural — `region_liveness_at_split_l1_gen` / T_Region_Active_L1 `binder = rv` sub-case; **GENUINELY FALSE as stated** (single-case counterexample documented at site lines 1923-1926); current `Admitted.` is a transparency mark, not "almost done" | L2 effect-typed `TFun` per `PRESERVATION-DESIGN.md §5.1`; closure requires reformulation, not direct proof | -| `Semantics_L1.v:2014` (`admit.`) | **Parallel mirror** of `:1994` — T_Region_Active_L1_Echo `binder = rv` sub-case; inherits same falsity | Same as `:1994` | -| `Semantics_L1.v:2028` (`Admitted.`) | **Outer marker** — depends on internal `:1994` + `:2014` | When both internal admits close (i.e., never directly — only via reformulation) | -| `Semantics_L1.v:3132` (`admit.`) | **Pre-existing** — `preservation_l1` body, covering S_StringConcat_Step2 + S_App_Step2 + S_Pair_Step2 cases; lambda-rigidity gap per `PRESERVATION-DESIGN.md §4.8` | Phase 3b Stages 2/3/4 (#240/#241/#242) — L2 effect-typed lambdas with `R_in/R_out` syntactic annotations | -| `Semantics_L1.v:3133` (`Admitted.`) | **Outer marker** — depends on internal `:3132` | When `:3132` closes | -| `Semantics.v:9257` (`Admitted.`) | **🛑 Sacrosanct** — legacy `Theorem preservation`, **provably false** per `Counterexample.v` (owner directive 2026-05-27) | Never. The `Admitted.` is correct. | - -**Falsity audit** — two admits are not "unproven but true"; they are -**provably false as stated**: - -1. `Semantics.v:9257` — refuted by `Counterexample.v`; sacrosanct. -2. `Semantics_L1.v:1994` (and its mirror `:2014`) — refuted by single-case - counterexample `ERegion rv (EI32 5)` at `R=[rv]` documented in the - source at lines 1923-1926. Their `Admitted.` is a transparency mark - acknowledging the residual sub-case is false. Closure requires - reformulation at the L2 layer (lambda-rigidity gap), not a direct proof. - -No `Axiom` declarations in `formal/*.v`. `Counterexample.v` / -`Counterexample_L2.v` / `Counterexample_L2_nested.v` carry 5 + 5 + 5 -Qed respectively; `TypingL1.v` / `Modality.v` / `Echo.v` / `TypingL2.v` -are all `admit.`-free. - -### Idris2 side (proof carriers, not Coq mechanisation) - -| Concern | File(s) | Status | -|---|---|---| -| Region linearity | `src/formal/Ephapax/Formal/RegionLinear.idr` | working — explicitly "REAL proof — not believe_me, not assert_total" | -| ABI surface (17 files) | `src/abi/Ephapax/…` | working — clean of `believe_me` / `sorry` | - -### Tools - -* **Coq 8.18+** for `formal/*.v` — primary mechanisation. -* **Idris2 0.8.0** (needs `IDRIS2_PREFIX` pointing at a prefix with TTCs) - for `src/abi/Ephapax/…`. -* **Just** as the build orchestrator (`just proofs`, `just idris-build`, - `just golden`). - -### Build oracles - -* `coqc 8.18.0` is the **only authoritative answer** to "is this proved?" - for `.v` files. Comments in source claiming `Qed` without `coqc` - acceptance are not evidence. -* `idris2 --check` is the same for `.idr` files. -* Both are wired into `rust-ci.yml`'s "Coq proofs" and "Idris2 build" - jobs. - ---- - -## §5. Closure plan + current-branch state + foundation audit (2026-06-16) - -> This section is a dated **status + plan** snapshot. It does **not** -> supersede §1–§4 (which track `main`); it records (a) the authoritative -> ground-truthed admit set on the current proof branch, (b) the committed -> closure route, and (c) the independent trust audit of the foundation -> repos the route builds on. Sourced from a ground-truth sweep -> (`coqc`/`grep`-verified, not doc-trusted), not from prose. -> -> **Companion design docs** `L1-REGION-REFOUNDATION-PLAN.md` and -> `L1-ELIMINATOR-FORK.md` (referenced below) live in **PR #299** -> (branch `docs/l1-closure-research`), pending merge to `formal/`. The -> tropical applicability case study is `tropical-resource-typing` PR #21 -> (`docs/applications/ephapax-l1-regions.adoc`). - -### 5.1 Current proof branch - -Branch `proofs/l1-effect-typing-Aprime` (A′ effect-typed-only lambdas). -**A′ closes 0 of 4 L1 admits** — the open obligations are region/eliminator -cases, not lambda-rigidity cases — so its adoption-vs-revert disposition is -still open (it is groundwork for the choreographic `S_App_Step2` case, not a -closure). - -All four L1 lemmas (`region_shrink`, `region_liveness`, `step_pop`, -`preservation_l1`) each carry an outer `Admitted.` on **both** this branch -**and `main`** (`step_pop_disjoint_from_type_l1` is present on `main` too, at -`Semantics_L1.v:3178`/`:3433`). So `formal/` holds **5 outer `Admitted.`** -(1 legacy + 4 L1) on main — **but the §4 `status-gate` marker reads `4` and -the §4 prose says "3 outer `Admitted.` in `Semantics_L1.v`": both are stale by -one** (the true `Semantics_L1.v` count is 4 outer markers, → 5 with the -legacy). This is a real **`main`-wide** drift, not branch-local — and exactly -the drift the **broken + unwired** `status-gate` (see §5.7) was supposed to -catch but couldn't. §4's marker is corrected to `5` in this PR; the companion -`fix/proof-gates` PR repairs the script (`//`→`#`) and wires it into CI so -future drift is flagged. (A′ shifts the line numbers but not the count.) - -### 5.2 The four L1 admits — authoritative classification (this branch) - -| # | Lemma | Site (this branch) | Internal admits | Class | Closes via | -|---|---|---|---|---|---| -| 1 | `region_shrink_preserves_typing_l1_gen_m` | `Semantics_L1.v:441` | `:572`, `:642` | **live structural** (list-vs-multiset tear) | **Dissolves** — carrier refactor `region_env : list` → count-map. `L1-REGION-REFOUNDATION-PLAN.md` | -| 2 | `region_liveness_at_split_l1_gen` | `Semantics_L1.v:1904` | `:1956`, `:1976` | **provably FALSE** as written (witness `ERegion rv (EI32 5)` at `R=[rv]`) | **Repaired** — restate as graded `live R rv ∧ no-exit-of-rv ⇒ live R' rv`; prove from `count_occ_le_l1_m` monotonicity + ~13-site call audit. `L1-REGION-REFOUNDATION-PLAN.md §5` | -| 3 | `step_pop_disjoint_from_type_l1` | `Semantics_L1.v` | **1** (reduced from ~9–11 in 2026 via the `step_R_change_shape` rewrite — see `L1-ELIMINATOR-FORK.md` §8). The single residual obligation is `step exits r0 ∧ r0 ∈ free_regions(T) ⟹ cnt r0 R ≥ 2`; its direct-exit sub-case is vacuous, the residue is congruence-exit count-coherence. Diagnosis corrected: **not** §4.8 lambda-rigidity (the witness has no lambda) but region-count coherence at a region exit. **⚠️ 2026-07-07 (experiment 3): provably FALSE AS STATED** — `formal/Counterexample_StepPop.v` (Qed, zero axioms) exhibits `EPair (ERegion rv (EI32 5)) (EVar 0)` at `R=[rv]`: it types (`T_Var_Lin_L1` has no `In rv R` premise), steps (`S_Region_Exit` under `S_Pair_Step1`), and lands at `R'=[]` with `rv ∈ free_regions(T)`. Same class as admit #2's history: the `Admitted.` hides a falsehood, not a gap. | **the eliminator fork** — now with a Qed forcing argument that typing ⇏ trace coherence | **Reformulation, owner-gated** — restate with a liveness/coherence premise or restrict to value-anchored consumers (the sole caller retypes an `ELoc` value whose `T_Loc_L1` typing carried `In r R`; the refuting sibling is a variable, which no `S_*_Step2` case retypes at `R'`). The choreographic route's task is now provably "make coherence a judgment-level input", not "derive it". `Counterexample_StepPop.v` header + `L1-ELIMINATOR-FORK.md` §3–§8 | -| 4 | `preservation_l1` (capstone) | `Semantics_L1.v:3367` | `:3379` | **⚠️ provably FALSE AS STATED (2026-07-07, experiment 3b)** — `formal/Counterexample_PreservationL1.v` (Qed, zero axioms): `EStringConcat (ELoc 0 rv) (ESnd (EPair (ERegion rv (EI32 0)) (EVar 0)))` at `R=[rv]` types in both modalities, steps (the sibling's region-exit pops the only `rv`), and the post-state is untypable — the `ELoc` anchor's `T_Loc_L1` demands `In rv []`. The value-anchored escape hatch suggested by `Counterexample_StepPop.v` is closed: the dangling dependence here IS a value. | **NOT closable by lemmas** — the judgment must carry the temporal invariant (region-liveness-through-reduction): either coherence as a judgment input (choreographic-foundational route, now Qed-mandatory) or leaf-rule strengthening that rejects the witness at typing (A′/path-3 family, to be re-examined against this witness). Owner decision; record in `PRESERVATION-DESIGN.md`. | - -`preservation_l3` (`Semantics_L1.v`, **Qed**) is real and depends on **only** -admit #1 (`region_shrink`); it flips **unconditional** the moment #1 closes. -`L4.v` is **scaffold-unbuilt** (`ProgramMode` + round-trip mapping; -definitions only, no theorems — not debt, just not yet built). No hidden 5th -admit, no `Axiom`/`Parameter`/`Hypothesis` smuggled assumption, no fake-`Qed` -was found anywhere in `formal/*.v`. - -### 5.3 Idris2 ABI proof needs (a separate axis from the Coq L1 admits) - -`src/abi/Ephapax/ABI/Invariants.idr` states six compiler-correctness -invariants as **erased (0-quantity) postulates**, each an explicit *owed* -forward to a discharging proof: - -| ID | Invariant | Status | -|---|---|---| -| E1 | Type preservation (operational) | **owed** — forwards to Coq `preservation` (legacy is false; real obligation is `preservation_l1`) | -| E2 | Linear consumption across control flow | E2a position-form **DISCHARGED** (`splitLinearCoverage`, Qed, `Formal/Qualifier.idr`); E2b control-flow form **owed** | -| E3 | No region escape | static/type-level **DISCHARGED** (`noEscapeTheorem`, Qed, `Formal/RegionLinear.idr`); operational form **owed** | -| E4 | No runtime GC | **owed + flagged** — `noGCExtract` is a **tautological/vacuous wrapper** (returns inputs unchanged), not a proof | -| E5 | WASM compilation correctness | **owed — no formalisation exists** (Rust backend only). Highest-leverage compiler-correctness gap; enables `compileOkImpliesWasmTyped` in `Foreign.idr` | -| E6 | IR (S-expr) lowering correctness | **owed — no formalisation** | - -Real Idris Qed proofs verified clean (no `believe_me`/`assert_total`/holes): -`splitLinearCoverage`, `noEscapeTheorem`, `orthogonalityLemma`, -`compileResultRoundTrip`, `outlivesTransitive`. Both `abi-verify.yml` and -`coq-build.yml` are hard CI gates (`idris2 --build`; `coq_makefile` + -`Print Assumptions` on the Phase-D top-level results). - -### 5.4 Committed closure route (owner-committed 2026-06-16) - -1. **Clean win (deterministic):** tropical carrier refactor → dissolves #1, - repairs the false #2, then flips `preservation_l3` unconditional. - **Net 4 → 2 admits, 0 false lemmas.** (`L1-REGION-REFOUNDATION-PLAN.md`.) -2. **Deciding experiment (cheap, do before the general theory):** the minimal - `EDrop (EVar j : TString rv)` choreography over two segments — does subject - reduction carry liveness through `S_Region_Exit rv` coherently, or relocate - it into a projection-coherence side-condition? (`L1-ELIMINATOR-FORK.md §6`.) - **DONE** (`formal/L1ChoreoExperiment.v`): verdict **relocates** — and it - sharpened *why* (the naive model collapses projection onto the scalar - predicate, which is false at re-entry; the real fork lives in the - **congruence** cases, not the clean `ERegion` exit). -2b. **Congruence experiment (non-collapsed model):** **DONE** - (`formal/L1ChoreoExperiment2.v`, `L1-ELIMINATOR-FORK.md §9`). A trace model - that keeps subterm-relative **order** (not just the scalar balance) - **closes — in the model** — the obligation `step_pop` relocates to: - `sibling_use_keeps_region_live` is `Qed`, axiom-free, non-circular, and - unprovable from the snapshot alone. The congruence verdict is upgraded from - "relocates" to "closes in the trace model, reduced to **one wiring lemma**" - (`wiring_obligation`: typing ⇒ `valid` trace at `k = cnt rv R`). Experiment - 1's re-entry failure mode provably does not recur. **Closes no admit yet** - (honest count still 3); it is the consolidation step that names the precise - next target. -3. **If green → choreographic-foundational re-derivation:** region liveness as - a tropically-graded choreography across time segments (regions = session- - typed resources, scopes = segments, preservation = subject reduction). - Closes #3 → **2 → 0**, and likely gives **L4** its dyadic (2-party - choreography) structure for free — one foundation, all four layers. **Next - concrete step:** discharge `wiring_obligation` (plausibly a structural - induction on the typing derivation — each trace `Close`/`Use` `>= 1` premise - mirrors a local `In r R` premise in `T_Region_Active_L1` / `T_Loc_L1`). - -### 5.5 Foundation trust audit (2026-06-16) — GO - -The route reuses proofs from three sibling repos. An independent ground-truth -audit (cold rebuilds + kernel/flag probes, not doc-trust) cleared all three — -the owner's "did an LLM let a blank/sorry through?" concern **did not -materialise**: - -* **`epistemic-types`** (Agda 2.8.0) — **trustworthy/GO.** `agda - --no-libraries … All.agda` exit 0; `-W error` still exit 0; `--safe` - *proven enforced* (postulate-injection probe rejected, exit 42); zero - escape hatches; zero orphans. Caveat: its `EchoBridge` is a deliberate - scaffold (grade plumbing, no proven graded-loss laws). -* **`tropical-resource-typing`** (Lean 4.13.0) — **trustworthy/GO (strongest).** - Cold `lake build` exit 0 ×2; kernel `#print axioms` on all 17 load-bearing - results → only `propext`/`Quot.sound` or none (**no `sorryAx`, no - `Classical.choice`**). Every lemma the route reuses is non-vacuous. -* **`echo-types`** (Agda 2.8.0) — **trustworthy/GO, build on the WIRED layer - only.** 164 wired files (`--safe --without-K`, exit 0, zero postulates) vs - 15 orphaned (~8%). Three caveats: (a) the **one** real postulate lives in - the orphaned, exempted exploratory `EchoImageFactorizationPropPostulated.agda` - (honest quarantine, imported by nothing); (b) the experimental - `echo-additive` **variance** suite is **orphaned + RETRACTED (R-2026-05-18)** - and `VarianceGate.agda` self-declares "NO proven theorems" — **do not cite - variance as proven**; (c) two dangling `libraries` configs cause false - "name not found" failures — fix before handing off. - -### 5.7 Gate integrity — threatened (surfaced 2026-06-16, NOT yet fixed) - -Two CI-gate findings threaten the "is it proved?" guarantee and are -recorded here so they are not lost (no fix applied — surfaced for decision): - -* **`coq-build-gate` is implemented but NOT a *required* status check.** - `.github/settings.yml:113-118` requires only `hypatia-scan` + `codeql`; - the always-on `coq-build-gate` aggregator (PR #254) is not in - `required_status_checks`. **Consequence: a new `Admitted.`/`Axiom`, or a - proof that fails `coqc`, could merge to `main` undetected.** This is the - single highest-priority gate fix. -* **`scripts/status-gate.sh` is doubly dead:** (a) line 3 is a `//` C-style - comment (should be `#`) → the script is syntactically invalid and aborts - immediately (a delimiter bug, fixable without touching the owner string); - (b) it is wired into no CI workflow (only `just status-gate`). So the - "Coq admitted proofs remaining: N" drift check in §4 never runs in CI — - which is also why the §5.1 count drift (4 vs 5 on this branch) was never - flagged automatically. - -(Lower-severity: `scorecard-enforcer` defaults the parsed score to `0` on a -malformed SARIF → possible false-fail; `boj-build.yml` `continue-on-error` -is acceptable for an external service.) - -### 5.6 Bottom line - -ephapax's remaining proof work = the 4 L1 admits (→ 2 by the deterministic -clean win, → 0 if the eliminator-fork experiment goes green) + the unbuilt L4 -+ the owed Idris ABI obligations (E1, E2b, E3-operational, E4, E5, E6, with -E5 the highest leverage). The legacy `Semantics.v preservation` is **not -debt** — it is a deliberate, witnessed false result. The plan is staged, -ground-truthed, and the foundation it builds on is independently verified. - ---- - -## Cross-references - -* [`STATUS.adoc`](STATUS.adoc) — past / present / future temporal map. -* [`formal/PRESERVATION-DESIGN.md`](formal/PRESERVATION-DESIGN.md) — - canonical four-layer design doctrine. -* [`CLAUDE.md`](CLAUDE.md) — agent guidance; owner directive 2026-05-27. -* [`formal/Counterexample.v`](formal/Counterexample.v) — the 5 Qed - lemmas that pin the soundness gap. -* [`https://github.com/hyperpolymath/echo-types`](https://github.com/hyperpolymath/echo-types) - — upstream Agda foundation for L3. -* [Disambiguation: ephapax vs AffineScript](https://github.com/hyperpolymath/nextgen-languages/blob/main/docs/disambiguation/ephapax-vs-affinescript.md) - — canonical side-by-side table. diff --git a/SECURITY.adoc b/SECURITY.adoc new file mode 100644 index 00000000..e47fdd92 --- /dev/null +++ b/SECURITY.adoc @@ -0,0 +1,16 @@ +== Security Policy + +=== Reporting a Vulnerability + +If you discover a security vulnerability, please report it responsibly. + +*Email:* j.d.a.jewell@open.ac.uk + +*Please include:* - Description of the vulnerability - Steps to +reproduce - Potential impact + +*Response timeline:* - Acknowledgement within 48 hours - Initial +assessment within 7 days - Fix or mitigation within 90 days + +*Safe harbour:* We will not pursue legal action against security +researchers who follow responsible disclosure. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index d1e94982..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,21 +0,0 @@ - - -# Security Policy - -## Reporting a Vulnerability - -If you discover a security vulnerability, please report it responsibly. - -**Email:** j.d.a.jewell@open.ac.uk - -**Please include:** -- Description of the vulnerability -- Steps to reproduce -- Potential impact - -**Response timeline:** -- Acknowledgement within 48 hours -- Initial assessment within 7 days -- Fix or mitigation within 90 days - -**Safe harbour:** We will not pursue legal action against security researchers who follow responsible disclosure. diff --git a/TEST-NEEDS.adoc b/TEST-NEEDS.adoc new file mode 100644 index 00000000..cacf4df0 --- /dev/null +++ b/TEST-NEEDS.adoc @@ -0,0 +1,143 @@ +== Test & Benchmark Requirements + +=== CRG Grade: C — ACHIEVED 2026-04-04 + +=== Current State — CRG C ACHIEVED (2026-04-04) + +* Unit tests: 365 pass / 0 fail (across 17 active workspace crates + +`+tests/fuzz+`; count from +`+cargo test --workspace --lib -- --list | grep ': test$'+`) +* Integration tests: `+src/ephapax-cli/tests/integration.rs+` +(parse->type-check, 18 tests) +* Conformance tests: `+src/ephapax-cli/tests/conformance.rs+` (spec +compliance, 16 tests) +* E2E/WASM tests: `+src/ephapax-cli/tests/wasm_e2e.rs+` +(parse->type-check->compile->wasmtime, 13 tests) +* Property-based tests: `+src/ephapax-cli/tests/property_tests.rs+` +(proptest, 6 properties) +* Contract/invariant tests: `+src/ephapax-cli/tests/contract_tests.rs+` +(type system invariants, 13 tests) +* Aspect tests: `+src/ephapax-cli/tests/aspect_tests.rs+` (security, +performance, correctness, 13 tests) +* Benchmarks: `+src/ephapax-parser/benches/parse_bench.rs+`, +`+src/ephapax-vram-cache/benches/cache_bench.rs+` +* Total: *488 tests* (`+cargo test --workspace --all-targets+`); +pass/fail enforced by `+rust-ci.yml+` +* Documented all-target tests: 488 + +=== CRG Testing Taxonomy — Status + +[cols=",,",options="header",] +|=== +|Category |Status |Files +|Unit tests |DONE |17 active workspace crates + `+tests/fuzz+` +|Integration tests |DONE |`+tests/integration.rs+` +|Conformance tests |DONE |`+tests/conformance.rs+` +|E2E tests |DONE |`+tests/wasm_e2e.rs+` +|Property-based (proptest) |DONE |`+tests/property_tests.rs+` +|Contract/invariant |DONE |`+tests/contract_tests.rs+` +|Aspect tests |DONE |`+tests/aspect_tests.rs+` +|Benchmarks |DONE |2 bench files (criterion) +|=== + +=== What’s Still Missing (for CRG B+) + +==== Point-to-Point (P2P) + +*Source counts:* 49 Rust (19 crates) + 98 .eph files + 17 Idris2 + 3 V + +*Coq admitted proofs remaining: 3* (1 outer `+Admitted.+` in +`+formal/Semantics.v+` + 2 outer `+Admitted.+` markers in +`+formal/Semantics_L1.v+` — `+step_pop_disjoint_from_type_l1+` / +`+preservation_l1+` — covering 2 internal `+admit.+` cases at lines +3315/3336; rebuilt `+coqc 8.18.0+` ground truth 2026-06-27). Single- +source breakdown lives in `+PROOF-NEEDS.md §4+`; +`+scripts/status-gate.sh+` reads from there. + +===== Crates with ZERO tests: + +* Multiple crates show 0 tests in cargo test output +* codegen crates — no tests +* Several small utility crates — no tests + +===== .eph files (98): + +* Standard library .eph files not tested via automated runner +* Example programs not verified in CI + +===== Known issues: + +* 3 Admitted markers in Coq (1 in `+Semantics.v+` — provably-false +legacy preservation, sacrosanct; 2 in `+Semantics_L1.v+` — +`+step_pop_disjoint_from_type_l1+` + `+preservation_l1+`, the +eliminator-fork invariant + its gated capstone, per PROOF-NEEDS.md §4) +* 5 remaining tasks (#15-#19) from type checker audit +* interp env-leak fix was made 2026-03-28 + +==== End-to-End (E2E) + +* All 98 .eph example programs should compile and run (not automated) +* Multi-file compilation with imports +* REPL interaction (if exists) + +==== Build & Execution + +* [x] cargo build — compiles +* [x] cargo test –workspace –all-targets — 467 pass, 0 fail +* [x] idris2/ephapax-parse-tests.ipkg — compiles AND runs green +(2026-07-07, idris2 0.8.0 + zig-built libephapax_tokbuf.so); CI-gated +compile-only by abi-verify.yml `+idris2-parse-front+` (runtime run stays +local — the estate idris2 container has no zig) +* [ ] idris2/ephapax-affine.ipkg — NOT CI-gated: depends on the external +`+proven+` Idris2 package (hyperpolymath/proven), which the estate +idris2 image does not carry. Gate once proven is installable in CI (or +vendored). +* [ ] Compile and run all 98 .eph files — not automated +* [ ] CLI –help works — not verified +* [ ] Self-diagnostic — none + +==== Benchmarks Needed (for B+) + +* Parser throughput on large .eph files +* Type checker performance on complex linear type programs +* Comparison: linear type checking overhead vs non-linear +* Memory usage during compilation +* Verify 2 existing benchmark files actually run + +==== Self-Tests + +* [ ] panic-attack assail on own repo +* [ ] Compile all .eph stdlib files as test suite +* [ ] Resolve remaining Admitted Coq markers (`+preservation+` in +`+Semantics.v+` is sacrosanct — provably false; 3 in `+Semantics_L1.v+` +track pre-existing L1 structural debt — see PROOF-NEEDS.md §4) + +=== Priority + +* *CRG C* — ACHIEVED (2026-04-04). Property, contract, aspect, and +reflexive tests now present alongside existing +unit/integration/conformance/E2E tests. +* *CRG B* — Requires: coverage metrics, 6+ test targets per module, +zero-test crates remediated, .eph files tested as a suite. +* *CRG A* — Requires: fuzz harness, formal proof coverage, mutation +testing. + +=== FUZZ TESTING STATUS + +* ✅ Real fuzz testing infrastructure added in `+tests/fuzz/+` +* ✅ Parser fuzzer implemented (`+fuzz_targets/parse_fuzzer.rs+`) +* ✅ Type checker fuzzer implemented +(`+fuzz_targets/typecheck_fuzzer.rs+`) +* ✅ Seed corpus created with valid and invalid test cases +* ✅ Integrated into workspace Cargo.toml + +*Priority: P1 — Real fuzz coverage now exists* + +To run fuzz tests: + +[source,bash] +---- +cd tests/fuzz +cargo fuzz run parse_fuzzer -- -max_total_time=60 +cargo fuzz run typecheck_fuzzer -- -max_total_time=60 +---- diff --git a/TEST-NEEDS.md b/TEST-NEEDS.md deleted file mode 100644 index 104fccf5..00000000 --- a/TEST-NEEDS.md +++ /dev/null @@ -1,114 +0,0 @@ - - - -# Test & Benchmark Requirements - -## CRG Grade: C — ACHIEVED 2026-04-04 - -## Current State — CRG C ACHIEVED (2026-04-04) - -- Unit tests: 365 pass / 0 fail (across 17 active workspace crates + `tests/fuzz`; count from `cargo test --workspace --lib -- --list | grep ': test$'`) -- Integration tests: `src/ephapax-cli/tests/integration.rs` (parse->type-check, 18 tests) -- Conformance tests: `src/ephapax-cli/tests/conformance.rs` (spec compliance, 16 tests) -- E2E/WASM tests: `src/ephapax-cli/tests/wasm_e2e.rs` (parse->type-check->compile->wasmtime, 13 tests) -- Property-based tests: `src/ephapax-cli/tests/property_tests.rs` (proptest, 6 properties) -- Contract/invariant tests: `src/ephapax-cli/tests/contract_tests.rs` (type system invariants, 13 tests) -- Aspect tests: `src/ephapax-cli/tests/aspect_tests.rs` (security, performance, correctness, 13 tests) -- Benchmarks: `src/ephapax-parser/benches/parse_bench.rs`, `src/ephapax-vram-cache/benches/cache_bench.rs` -- Total: **488 tests** (`cargo test --workspace --all-targets`); pass/fail enforced by `rust-ci.yml` -- Documented all-target tests: 488 - -## CRG Testing Taxonomy — Status - -| Category | Status | Files | -|----------|--------|-------| -| Unit tests | DONE | 17 active workspace crates + `tests/fuzz` | -| Integration tests | DONE | `tests/integration.rs` | -| Conformance tests | DONE | `tests/conformance.rs` | -| E2E tests | DONE | `tests/wasm_e2e.rs` | -| Property-based (proptest) | DONE | `tests/property_tests.rs` | -| Contract/invariant | DONE | `tests/contract_tests.rs` | -| Aspect tests | DONE | `tests/aspect_tests.rs` | -| Benchmarks | DONE | 2 bench files (criterion) | - -## What's Still Missing (for CRG B+) - -### Point-to-Point (P2P) -**Source counts:** 49 Rust (19 crates) + 98 .eph files + 17 Idris2 + 3 V - -**Coq admitted proofs remaining: 3** (1 outer `Admitted.` in -`formal/Semantics.v` + 2 outer `Admitted.` markers in -`formal/Semantics_L1.v` — `step_pop_disjoint_from_type_l1` / -`preservation_l1` — covering 2 internal `admit.` cases at lines -3315/3336; rebuilt `coqc 8.18.0` ground truth 2026-06-27). Single- -source breakdown lives in `PROOF-NEEDS.md §4`; `scripts/status-gate.sh` -reads from there. - -#### Crates with ZERO tests: -- Multiple crates show 0 tests in cargo test output -- codegen crates — no tests -- Several small utility crates — no tests - -#### .eph files (98): -- Standard library .eph files not tested via automated runner -- Example programs not verified in CI - -#### Known issues: -- 3 Admitted markers in Coq (1 in `Semantics.v` — provably-false legacy preservation, sacrosanct; 2 in `Semantics_L1.v` — `step_pop_disjoint_from_type_l1` + `preservation_l1`, the eliminator-fork invariant + its gated capstone, per PROOF-NEEDS.md §4) -- 5 remaining tasks (#15-#19) from type checker audit -- interp env-leak fix was made 2026-03-28 - -### End-to-End (E2E) -- All 98 .eph example programs should compile and run (not automated) -- Multi-file compilation with imports -- REPL interaction (if exists) - -### Build & Execution -- [x] cargo build — compiles -- [x] cargo test --workspace --all-targets — 467 pass, 0 fail -- [x] idris2/ephapax-parse-tests.ipkg — compiles AND runs green (2026-07-07, - idris2 0.8.0 + zig-built libephapax_tokbuf.so); CI-gated compile-only by - abi-verify.yml `idris2-parse-front` (runtime run stays local — the estate - idris2 container has no zig) -- [ ] idris2/ephapax-affine.ipkg — NOT CI-gated: depends on the external - `proven` Idris2 package (hyperpolymath/proven), which the estate idris2 - image does not carry. Gate once proven is installable in CI (or vendored). -- [ ] Compile and run all 98 .eph files — not automated -- [ ] CLI --help works — not verified -- [ ] Self-diagnostic — none - -### Benchmarks Needed (for B+) -- Parser throughput on large .eph files -- Type checker performance on complex linear type programs -- Comparison: linear type checking overhead vs non-linear -- Memory usage during compilation -- Verify 2 existing benchmark files actually run - -### Self-Tests -- [ ] panic-attack assail on own repo -- [ ] Compile all .eph stdlib files as test suite -- [ ] Resolve remaining Admitted Coq markers (`preservation` in `Semantics.v` is sacrosanct — provably false; 3 in `Semantics_L1.v` track pre-existing L1 structural debt — see PROOF-NEEDS.md §4) - -## Priority -- **CRG C** — ACHIEVED (2026-04-04). Property, contract, aspect, and reflexive tests - now present alongside existing unit/integration/conformance/E2E tests. -- **CRG B** — Requires: coverage metrics, 6+ test targets per module, zero-test - crates remediated, .eph files tested as a suite. -- **CRG A** — Requires: fuzz harness, formal proof coverage, mutation testing. - -## FUZZ TESTING STATUS - -- ✅ Real fuzz testing infrastructure added in `tests/fuzz/` -- ✅ Parser fuzzer implemented (`fuzz_targets/parse_fuzzer.rs`) -- ✅ Type checker fuzzer implemented (`fuzz_targets/typecheck_fuzzer.rs`) -- ✅ Seed corpus created with valid and invalid test cases -- ✅ Integrated into workspace Cargo.toml - -**Priority: P1 — Real fuzz coverage now exists** - -To run fuzz tests: -```bash -cd tests/fuzz -cargo fuzz run parse_fuzzer -- -max_total_time=60 -cargo fuzz run typecheck_fuzzer -- -max_total_time=60 -``` diff --git a/TOPOLOGY.md b/TOPOLOGY.adoc similarity index 63% rename from TOPOLOGY.md rename to TOPOLOGY.adoc index 7a7bb630..3db464d4 100644 --- a/TOPOLOGY.md +++ b/TOPOLOGY.adoc @@ -1,34 +1,47 @@ - - - - - -> ## Post-counterexample doctrine — read before editing this file -> -> This TOPOLOGY map predates the 2026-05-26 counterexample landing in -> parts of its system architecture diagram. The canonical *current* -> architecture lives in: -> -> - [`STATUS.adoc`](STATUS.adoc) — past/present/future map -> - [`formal/PRESERVATION-DESIGN.md`](formal/PRESERVATION-DESIGN.md) — four-layer architecture -> - [`PROOF-NEEDS.md`](PROOF-NEEDS.md) — per-sublanguage proof debt -> - [`CLAUDE.md`](CLAUDE.md) — agent guidance + owner directive 2026-05-27 -> -> ### Layer mapping (formal/ contents post-2026-05-27) -> -> | Layer | Concern | File(s) | Status | -> |---|---|---|---| -> | **L1** | Region capabilities + capability environment threading | `formal/TypingL1.v` + `formal/Semantics_L1.v` | judgment 100%, semantics 15 Qed / 9 admits (L2-integration debt) | -> | **L2** | Structural modality (Linear vs Affine) | `formal/Modality.v` + `m : Modality` in `has_type_l1` | core landed, `linear_to_affine` Qed with zero axioms | -> | **L3** | Echo / residue (irreversibility evidence) | `formal/Echo.v` + upstream `hyperpolymath/echo-types` | calculus done (12 Qed), wiring into typing pending | -> | **L4** | Dyadic interaction semantics | (none yet) | design in `PRESERVATION-DESIGN.md §7` | -> | 🛑 | Legacy `formal/Semantics.v` + `formal/Typing.v` | archaeology | preservation provably false — see `formal/Counterexample.v` | - -# ephapax (selur) — Project Topology - -## System Architecture - -``` +____ +=== Post-counterexample doctrine — read before editing this file + +This TOPOLOGY map predates the 2026-05-26 counterexample landing in +parts of its system architecture diagram. The canonical _current_ +architecture lives in: + +* link:STATUS.adoc[`+STATUS.adoc+`] — past/present/future map +* link:formal/PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md+`] +— four-layer architecture +* link:PROOF-NEEDS.md[`+PROOF-NEEDS.md+`] — per-sublanguage proof debt +* link:CLAUDE.md[`+CLAUDE.md+`] — agent guidance + owner directive +2026-05-27 + +==== Layer mapping (formal/ contents post-2026-05-27) + +[width="100%",cols="25%,25%,25%,25%",options="header",] +|=== +|Layer |Concern |File(s) |Status +|*L1* |Region capabilities + capability environment threading +|`+formal/TypingL1.v+` + `+formal/Semantics_L1.v+` |judgment 100%, +semantics 15 Qed / 9 admits (L2-integration debt) + +|*L2* |Structural modality (Linear vs Affine) |`+formal/Modality.v+` + +`+m : Modality+` in `+has_type_l1+` |core landed, `+linear_to_affine+` +Qed with zero axioms + +|*L3* |Echo / residue (irreversibility evidence) |`+formal/Echo.v+` + +upstream `+hyperpolymath/echo-types+` |calculus done (12 Qed), wiring +into typing pending + +|*L4* |Dyadic interaction semantics |(none yet) |design in +`+PRESERVATION-DESIGN.md §7+` + +|🛑 |Legacy `+formal/Semantics.v+` + `+formal/Typing.v+` |archaeology +|preservation provably false — see `+formal/Counterexample.v+` +|=== +____ + +== ephapax (selur) — Project Topology + +=== System Architecture + +.... ┌─────────────────────────────────────────┐ │ AERIE SUITE │ │ (Orchestration Layer) │ @@ -52,11 +65,11 @@ │ ABI-FFI Standards .machine_readable/ │ │ Aerie Component IndieWeb2 Bastion │ └─────────────────────────────────────────┘ -``` +.... -## Completion Dashboard +=== Completion Dashboard -``` +.... COMPONENT STATUS NOTES ───────────────────────────────── ────────────────── ───────────────────────────────── CORE COMPONENT (SELUR) @@ -71,22 +84,23 @@ REPO INFRASTRUCTURE ───────────────────────────────────────────────────────────────────────────── OVERALL: ████████░░ ~80% Component stable, repo refining -``` +.... -## Key Dependencies +=== Key Dependencies -``` +.... Idris2 ABI ──────► Zig FFI ──────► Aerie (Vörðr) ──────► IPC Flow -``` +.... -## Update Protocol +=== Update Protocol This file is maintained by both humans and AI agents. When updating: -1. **After completing a component**: Change its bar and percentage -2. **After adding a component**: Add a new row in the appropriate section -3. **After architectural changes**: Update the ASCII diagram -4. **Date**: Update the `Last updated` comment at the top of this file +[arabic] +. *After completing a component*: Change its bar and percentage +. *After adding a component*: Add a new row in the appropriate section +. *After architectural changes*: Update the ASCII diagram +. *Date*: Update the `+Last updated+` comment at the top of this file -Progress bars use: `█` (filled) and `░` (empty), 10 characters wide. -Percentages: 0%, 10%, 20%, ... 100% (in 10% increments). +Progress bars use: `+█+` (filled) and `+░+` (empty), 10 characters wide. +Percentages: 0%, 10%, 20%, … 100% (in 10% increments). diff --git a/conformance/README.adoc b/conformance/README.adoc new file mode 100644 index 00000000..7ae97539 --- /dev/null +++ b/conformance/README.adoc @@ -0,0 +1,38 @@ +== Ephapax Conformance Test Suite + +Type system conformance tests for the Ephapax compiler. + +=== Structure + +* `+valid/+` — Programs that MUST type-check successfully. +* `+invalid/+` — Programs that MUST be rejected with specific error +codes. + +=== Error Codes + +[width="100%",cols="12%,16%,21%,51%",options="header",] +|=== +|Code |Category |Description |`+DisciplineViolation+` variant +|E001 |Linear |Linear variable not consumed |`+WeakeningForbidden+` + +|E002 |Linear |Variable used after consumption |`+Contraction+` + +|E003 |Region |Value escapes its region |(typing-rule +`+NoRegionInType+`) + +|E004 |Region |Linear binding in region not consumed before region exit +|`+RegionLeakLinear+` + +|E005 |Branch |Branches disagree on consumption of a linear variable +|`+BranchDisagreement+` +|=== + +Ground-truth source: `+DisciplineViolation+` enum in +`+ephapax-linear/src/lib.rs+`. + +=== Running + +[source,bash] +---- +just conformance +---- diff --git a/conformance/README.md b/conformance/README.md deleted file mode 100644 index dee803b7..00000000 --- a/conformance/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Ephapax Conformance Test Suite - - - - - -Type system conformance tests for the Ephapax compiler. - -## Structure - -- `valid/` — Programs that MUST type-check successfully. -- `invalid/` — Programs that MUST be rejected with specific error codes. - -## Error Codes - -| Code | Category | Description | `DisciplineViolation` variant | -|------|----------|-------------|-------------------------------| -| E001 | Linear | Linear variable not consumed | `WeakeningForbidden` | -| E002 | Linear | Variable used after consumption | `Contraction` | -| E003 | Region | Value escapes its region | (typing-rule `NoRegionInType`) | -| E004 | Region | Linear binding in region not consumed before region exit | `RegionLeakLinear` | -| E005 | Branch | Branches disagree on consumption of a linear variable | `BranchDisagreement` | - -Ground-truth source: `DisciplineViolation` enum in `ephapax-linear/src/lib.rs`. - -## Running - -```bash -just conformance -``` diff --git a/docs/governance/HYPATIA-HANDOFF.adoc b/docs/governance/HYPATIA-HANDOFF.adoc new file mode 100644 index 00000000..f1cd3bc0 --- /dev/null +++ b/docs/governance/HYPATIA-HANDOFF.adoc @@ -0,0 +1,111 @@ +== Hypatia findings — outstanding triage handoff (2026-06-24) + +____ +*Status: OPEN.* The Hypatia neurosymbolic gate is now active and reports +*37 pre-existing findings (5 critical, 7 high, 25 medium)* on ephapax. +They are *not* fixed — triage + fix is the work below. These are +pre-existing repo debt that a broken CI pin had been masking, *not* +introduced by the change that unmasked them. +____ + +=== How this surfaced + +The `+scan / Hypatia Neurosymbolic Analysis+` gate used to die at +"`Prepare all required actions`" because the three +`+hyperpolymath/standards+` reusable workflows were pinned to +`+5a93d9d57cc0+`, a ref that is *not* an ancestor of `+standards+` HEAD; +that broken pin also transitively referenced a dead `+actions/cache+` +SHA. PR #315 re-pinned to a published HEAD (later bumped to `+d135b05…+` +in #316), which let the scan actually run — and report the 37 findings +that were previously invisible. + +____ +*Two milestones are not one:* "`the workflow now __runs__`" and "`the +workflow now __passes__`" are different. Fixing the pin achieved the +first; the second is this document. +____ + +=== Why this is handed off (cannot be run in the remote sandbox) + +The engine is an Elixir escript in `+github.com/hyperpolymath/hypatia+`. +Building it needs `+mix deps.get+`, which fetches from *`+repo.hex.pm+`* +— and the remote session’s egress policy returns `+403+` for the Hex +registry (npm/PyPI/crates/Go are allowed; Hex is not). So the engine +must be run on a host where Hex is reachable (a local machine, or a CI +runner), *or* the environment’s network policy must be widened to allow +`+repo.hex.pm+` + `+builds.hex.pm+`. + +=== Task 1 — produce the exact findings + +[source,bash] +---- +git clone https://github.com/hyperpolymath/hypatia.git ~/hypatia +cd ~/hypatia +mix local.hex --force && mix local.rebar --force +mix deps.get +mix escript.build +# Run with NO GITHUB_TOKEN to match CI (CI had none, so the dependabot / +# secret-scanning / code-scanning alert rules emit nothing — the 37 are +# file-based rules): +HYPATIA_FORMAT=json ./hypatia-cli.sh scan /path/to/ephapax > hypatia-findings.json +jq '[.[].severity] | group_by(.) | map({(.[0]): length}) | add' hypatia-findings.json +---- + +* Rules are pure functions in `+lib/rules/*.ex+`; `+scan+` runs +`+@all_rule_modules+`. +* With no token the 37 are file-based — most likely from +`+honest_completion+`, `+proof_obligation+`, `+disambiguation_rules+`, +`+code_safety+`, `+root_hygiene+`, `+supply_chain+`, +`+structural_drift+`, `+workflow_audit+`, `+workflow_hardening+`, +`+cicd_rules+`, `+scorecard_compliance+`. (A local token may surface +MORE than 37 — run without one to reproduce the gate’s set.) +* Severities depend on the pinned `+standards+` reusable-workflow +version (currently `+d135b05…+`); the 5/7/25 split was observed on the +first unblocked run and may shift slightly with the engine version. + +=== Task 2 — triage + fix + +[arabic] +. Group by rule × severity. Fix order: *5 critical → 7 high → 25 +medium*. +. Per finding: true positive → fix in code/docs; false positive / +accepted → add a justified `+rule:path+` line to `+.hypatia-ignore+` +(matched `+grep -qxF+`), or enter it in `+.hypatia-baseline.json+` +(schema in `+standards+` +`+.machine_readable/hypatia-baseline.schema.json+`). +. *Proof / claim findings* (`+honest_completion+`, +`+proof_obligation+`): fix by making _claims_ honest (docs/metadata), +*never* by editing or force-closing the fenced legacy proofs. Per +`+CLAUDE.md+`: do not touch `+formal/Semantics.v+` +`+Theorem preservation+` (provably false; deliberately `+Admitted+`), +`+formal/Typing.v+`, or `+formal/Counterexample.v+`. +. *`+disambiguation_rules+`* findings: ensure the +ephapax-vs-AffineScript disambiguation markers are present per +`+CLAUDE.md+`. +. Re-run the scan until `+critical = 0+` (ideally total within policy). +Open a PR with a triage table: _finding → disposition → fix_. + +=== Task 3 — make the gate non-opaque (in `+hyperpolymath/standards+`) + +The gate fails *opaquely*: in `+standards+`’ +`+.github/workflows/hypatia-scan-reusable.yml+` the scan runs under +`+bash -e+`, and `+hypatia-cli.sh scan+` *exits 1 when findings exist*, +so the step aborts _before_ the (already-present) `+upload-artifact+` +step and the "`warn but don’t fail`" critical-check ever run. Net +effect: a hard job failure emitting only a severity count, no detail. + +*Fix (in `+standards+`):* wrap the scan in `+set +e+` + capture the rc; +*always* upload `+hypatia-findings.json+` (`+if: always()+`); write a +findings table to `+$GITHUB_STEP_SUMMARY+`; enforce the gate in a final +dedicated step. Pin `+actions/upload-artifact+` by SHA (same +supply-chain lesson as PR #315). A full suggested patch was drafted +in-session (not yet applied). + +=== Guardrails (from `+CLAUDE.md+`) + +* This is `+hyperpolymath/ephapax+` (not AffineScript). Signed commits. +* Code/config = `+MPL-2.0+`; prose (`+.md+`/`+.adoc+`) = +`+CC-BY-SA-4.0+`. +* Never touch the fenced legacy preservation proof or `+Typing.v+` / +`+Counterexample.v+`. Fix proof/claim findings by aligning claims to +reality. diff --git a/docs/governance/HYPATIA-HANDOFF.md b/docs/governance/HYPATIA-HANDOFF.md deleted file mode 100644 index 1765a7da..00000000 --- a/docs/governance/HYPATIA-HANDOFF.md +++ /dev/null @@ -1,98 +0,0 @@ - - - - -# Hypatia findings — outstanding triage handoff (2026-06-24) - -> **Status: OPEN.** The Hypatia neurosymbolic gate is now active and reports -> **37 pre-existing findings (5 critical, 7 high, 25 medium)** on ephapax. -> They are **not** fixed — triage + fix is the work below. These are -> pre-existing repo debt that a broken CI pin had been masking, **not** -> introduced by the change that unmasked them. - -## How this surfaced - -The `scan / Hypatia Neurosymbolic Analysis` gate used to die at "Prepare all -required actions" because the three `hyperpolymath/standards` reusable -workflows were pinned to `5a93d9d57cc0`, a ref that is **not** an ancestor of -`standards` HEAD; that broken pin also transitively referenced a dead -`actions/cache` SHA. PR #315 re-pinned to a published HEAD (later bumped to -`d135b05…` in #316), which let the scan actually run — and report the 37 -findings that were previously invisible. - -> **Two milestones are not one:** "the workflow now *runs*" and "the workflow -> now *passes*" are different. Fixing the pin achieved the first; the second -> is this document. - -## Why this is handed off (cannot be run in the remote sandbox) - -The engine is an Elixir escript in `github.com/hyperpolymath/hypatia`. Building -it needs `mix deps.get`, which fetches from **`repo.hex.pm`** — and the remote -session's egress policy returns `403` for the Hex registry (npm/PyPI/crates/Go -are allowed; Hex is not). So the engine must be run on a host where Hex is -reachable (a local machine, or a CI runner), **or** the environment's network -policy must be widened to allow `repo.hex.pm` + `builds.hex.pm`. - -## Task 1 — produce the exact findings - -```bash -git clone https://github.com/hyperpolymath/hypatia.git ~/hypatia -cd ~/hypatia -mix local.hex --force && mix local.rebar --force -mix deps.get -mix escript.build -# Run with NO GITHUB_TOKEN to match CI (CI had none, so the dependabot / -# secret-scanning / code-scanning alert rules emit nothing — the 37 are -# file-based rules): -HYPATIA_FORMAT=json ./hypatia-cli.sh scan /path/to/ephapax > hypatia-findings.json -jq '[.[].severity] | group_by(.) | map({(.[0]): length}) | add' hypatia-findings.json -``` - -- Rules are pure functions in `lib/rules/*.ex`; `scan` runs `@all_rule_modules`. -- With no token the 37 are file-based — most likely from `honest_completion`, - `proof_obligation`, `disambiguation_rules`, `code_safety`, `root_hygiene`, - `supply_chain`, `structural_drift`, `workflow_audit`, `workflow_hardening`, - `cicd_rules`, `scorecard_compliance`. (A local token may surface MORE than - 37 — run without one to reproduce the gate's set.) -- Severities depend on the pinned `standards` reusable-workflow version - (currently `d135b05…`); the 5/7/25 split was observed on the first - unblocked run and may shift slightly with the engine version. - -## Task 2 — triage + fix - -1. Group by rule × severity. Fix order: **5 critical → 7 high → 25 medium**. -2. Per finding: true positive → fix in code/docs; false positive / accepted → - add a justified `rule:path` line to `.hypatia-ignore` (matched `grep -qxF`), - or enter it in `.hypatia-baseline.json` (schema in `standards` - `.machine_readable/hypatia-baseline.schema.json`). -3. **Proof / claim findings** (`honest_completion`, `proof_obligation`): fix by - making *claims* honest (docs/metadata), **never** by editing or force-closing - the fenced legacy proofs. Per `CLAUDE.md`: do not touch - `formal/Semantics.v` `Theorem preservation` (provably false; deliberately - `Admitted`), `formal/Typing.v`, or `formal/Counterexample.v`. -4. **`disambiguation_rules`** findings: ensure the ephapax-vs-AffineScript - disambiguation markers are present per `CLAUDE.md`. -5. Re-run the scan until `critical = 0` (ideally total within policy). Open a - PR with a triage table: *finding → disposition → fix*. - -## Task 3 — make the gate non-opaque (in `hyperpolymath/standards`) - -The gate fails **opaquely**: in `standards`' -`.github/workflows/hypatia-scan-reusable.yml` the scan runs under `bash -e`, -and `hypatia-cli.sh scan` **exits 1 when findings exist**, so the step aborts -*before* the (already-present) `upload-artifact` step and the "warn but don't -fail" critical-check ever run. Net effect: a hard job failure emitting only a -severity count, no detail. - -**Fix (in `standards`):** wrap the scan in `set +e` + capture the rc; **always** -upload `hypatia-findings.json` (`if: always()`); write a findings table to -`$GITHUB_STEP_SUMMARY`; enforce the gate in a final dedicated step. Pin -`actions/upload-artifact` by SHA (same supply-chain lesson as PR #315). A full -suggested patch was drafted in-session (not yet applied). - -## Guardrails (from `CLAUDE.md`) - -- This is `hyperpolymath/ephapax` (not AffineScript). Signed commits. -- Code/config = `MPL-2.0`; prose (`.md`/`.adoc`) = `CC-BY-SA-4.0`. -- Never touch the fenced legacy preservation proof or `Typing.v` / - `Counterexample.v`. Fix proof/claim findings by aligning claims to reality. diff --git a/docs/proof-debt.adoc b/docs/proof-debt.adoc index 52341e4f..6ee342fa 100644 --- a/docs/proof-debt.adoc +++ b/docs/proof-debt.adoc @@ -1,254 +1,125 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Owner: Jonathan D.A. Jewell -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -= Proof debt (SUPERSEDED — archaeology only) -:policy: https://github.com/hyperpolymath/standards/blob/main/docs/TRUSTED-BASE-REDUCTION-POLICY.adoc - -[IMPORTANT] -==== -*This document is SUPERSEDED.* It was written against the pre-2026-05-26 -legacy-judgment closure plan (the `step_preserves_type` / `step_output_context_eq` / -`preservation` reduction-from-910-goals story) and references a now-merged -branch `proof/l1-region-threading-design`. That entire framing is now -banned per owner directive 2026-05-27 — `Theorem preservation` in -`formal/Semantics.v` is *provably false* via `formal/Counterexample.v` -(five Qed lemmas) and its `Admitted.` is correct + durable. - -*Canonical proof-debt sources*: - -* `PROOF-NEEDS.md` (root) — current per-sublanguage proof-debt audit -* `formal/PRESERVATION-DESIGN.md §12` — four-layer redesign + per-layer - preservation strategy -* `formal/PHASE-D-REDESIGN.md` — current active workstream -* `formal/PRESERVATION-HANDOFF.md` — historical record (also marked - ARCHAEOLOGY at top) - -The remainder of this file is retained only for traceability of the -trusted-base-reduction-policy categories `(a)–(d)` against -link:{policy}[standards#203], and to point readers at the canonical -sources above. -==== - -== (a) Discharged in this repo - -No outstanding entries — discharged proofs are removed from this list -when they land. Recent discharges of note (kept here for one release -cycle for visibility): - -* `formal/Semantics.v` `step_preserves_type` — Qed via Path 3 at-pre - helper (2026-05-26, PR #146). -* `formal/Semantics.v` `step_output_context_eq` — Qed via Path 3 - at-pre helper (2026-05-26, PR #146). -* `formal/Semantics.v` `region_shrink_preserves_typing` — Qed - upstream of the at-pre helper. -* `formal/Semantics.v` `region_env_perm_typing`, - `remove_first_then_cons_membership_eq` — Qed; plug-in lemmas for - the S_Region_Step cross-case collapse. - -== (b) Budgeted — tested with refutation budget - -No entries. The ephapax core (Coq `formal/` + Rust `ephapax-linear/` -+ Idris2 `src/formal/Ephapax/Formal/…/*.idr`) contains zero `unsafe` -blocks, `unsafePerformIO`, `believe_me`, `assert_total`, or analogous -escape hatches at soundness-relevant positions. - -* `src/formal/Ephapax/Formal/RegionLinear.idr:127` mentions - `believe_me` only in a documentation comment ("not `believe_me`") - asserting the absence of the escape hatch — this is a negation, not - a use. - -If future FFI / extraction work introduces a `TRUSTED:` entry, the -`boj-server` backend-assurance harness is the reference implementation -for refutation budgets (`docs/proof-debt.adoc` §(b) in that repo). - -== (c) Necessary axiom - -`formal/Semantics.v` contains zero Coq `Axiom` declarations. - -* The "Axiom cases" mentioned in comments at `formal/Semantics.v:8619` - and `formal/Semantics.v:9225` refer to the *axiomatic step rules* - `S_StringNew` / `S_StringConcat` (operational-semantics primitives - that produce `ELoc l r` and require explicit reconstruction in - preservation), not Coq `Axiom` declarations. - -=== `idris2/src/Main.idr:21` — `%default partial` - -* **Kind**: extraction-boundary IO non-termination. The - ephapax-affine CLI driver `idris2/src/Main.idr` reads files, - writes outputs, and exits via `System.exitWith`. None of these - operations can be proven total within Idris2 0.8.0 without a - foreign-totality oracle for system primitives. -* **Justification**: standard practice for CLI `Main` modules in - Idris2. The non-totality is structurally inherited from - `System.File.*` and `System.exitWith`. Discharging would require - oracles for the OS (file existence at read time, disk space at - write time, process termination). -* **Scope**: limited to the CLI driver entry point. The pure - pipeline (`Ephapax.IR.*`, `Ephapax.Parse.*`, - `Ephapax.Affine.Typecheck`, `Ephapax.Affine.Emit`) is total under - the implicit (per-module) `%default total` of those modules. -* **Citation**: this pattern matches the - link:https://github.com/hyperpolymath/boj-server[boj-server] - backend-assurance harness's handling of IO-bearing entry points - — see `boj-server/docs/proof-debt.md` for the reference shape. - -== (d) DEBT — actively to be closed - -=== `formal/Semantics.v:9175` — `Admitted` `preservation` - -* **Owner**: @hyperpolymath -* **Status**: 1 `Admitted` blocking 11 cascading sub-goals on `main`. - Reduced from 910 → 22 → 12 → 11 across PRs - https://github.com/hyperpolymath/ephapax/pull/92[#92], - https://github.com/hyperpolymath/ephapax/pull/102[#102], - https://github.com/hyperpolymath/ephapax/pull/106[#106], - https://github.com/hyperpolymath/ephapax/pull/114[#114], - https://github.com/hyperpolymath/ephapax/pull/116[#116], and - https://github.com/hyperpolymath/ephapax/pull/146[#146] (98.8 % - reduction). - See link:{handoff}[`formal/PRESERVATION-HANDOFF.md`] for the per-PR - diagnostic narrative. -* **Load-bearing caveat**: the `preservation` theorem as stated on - `main` is *not just unproven — it is FALSE*. - link:{counterexample}[`formal/Counterexample.v`] on branch - `proof/l1-region-threading-design` exhibits a verified - counterexample (three lemmas `Qed`): an expression - `EPair (ERegion r1 (ELoc l0 r0)) (ELoc l1 r1)` that types at - `[r0; r1]`, single-steps under the current rules, and the resulting - expression does not type at any region environment derived from - `[r0]`. Sibling sub-expressions in compound forms (`T_Pair`, - `T_Let`, `T_App`, `T_StringConcat`, `T_If`, `T_Case`, …) share the - same region environment `R`; if one sibling exits a region the next - sibling depends on, the typing judgment has no place to record that - dependency. Closing the remaining 11 sub-goals on `main` is - therefore *not* the closure path. -* **Plan**: the four-layer redesign at - link:{design}[`formal/PRESERVATION-DESIGN.md`] (branch - `proof/l1-region-threading-design`; owner interpretation B ratified - 2026-05-26 — Echo as L3 residue, with interpretation A "Echo as - operational foundation" parked as a v2 research track in §12.16): -+ --- -** **L1** — restate `has_type` to thread `R_in / R_out` the same way - the linearity context `G` is already threaded; reprove - preservation; `Counterexample.v` becomes a regression test. - In-flight on the design branch (PRs - https://github.com/hyperpolymath/ephapax/pull/155[#155], - https://github.com/hyperpolymath/ephapax/pull/157[#157], - https://github.com/hyperpolymath/ephapax/pull/158[#158], - https://github.com/hyperpolymath/ephapax/pull/159[#159], - https://github.com/hyperpolymath/ephapax/pull/160[#160], - https://github.com/hyperpolymath/ephapax/pull/161[#161], - https://github.com/hyperpolymath/ephapax/pull/162[#162], - https://github.com/hyperpolymath/ephapax/pull/163[#163]): - `Semantics_L1.v` skeleton + `preservation_l1` stated; 29/33 - cases closed; region-env weakening + region-liveness invariant - landed; 4 admits surface a residual R-weakening gap; 2 of 6 - weakening admits closed. -** **L2** — modality parameter on `has_type` (`m ∈ {Linear, - Affine}`) + Linear ⇒ Affine weakening lemma. This introduces a - mechanised `ephapax-affine` judgment for the first time; without - L2, the affine sublanguage has only Rust `AffineChecker` - behavioural tests. -** **L3** — `formal/Echo.v` (residue / proof-carrying region exit + - drop). Theory upstream at `~/developer/repos/echo-types/`. -** **L4** — dyadic mode-elaboration soundness (research-tier, months+, - not in scope for the L1/L2/L3 chain). --- -* **Deadline**: INDEFINITE on `main` until PR - https://github.com/hyperpolymath/ephapax/pull/153[#153] (the design - + L1 rollup) merges. After #153 lands, L1 closure deadline becomes - 2026-Q3 (3–6 weeks focused work per the design doc estimate). -* **Cross-references**: - link:{roadmap}[`ROADMAP.adoc` § "Preservation closure plan"], - `formal/PRESERVATION-DESIGN.md` §12.15 (proof-debt status section), - `formal/PRESERVATION-DESIGN.md` §12.15.4 (what L1 does *not* - close — namely the affine side, which needs L2 + the weakening - lemma). - -=== `formal/Semantics_L1.v` — in-flight L1 admits (post-#176 L2 hybrid) - -After the L2 hybrid landing (#176), main's `Semantics_L1.v` carries -the following escape-hatch sites. All are tracked, structurally -diagnosed, and sequenced under the four-layer redesign plan. - -NOTE: line numbers are point-in-time and may shift with subsequent -edits. The substring match is `file:line`; entries below cover the -current positions on main. - -* **`formal/Semantics_L1.v:343`** — `Admitted.` capping one of the - region-shrink helpers (T_Region_Active_L1 shadowed case at the - list-vs-multiset boundary). Three documented closure approaches - in-file. -* **`formal/Semantics_L1.v:400`** — `Admitted.` (related - region-shrink admit; same structural class as L343). -* **`formal/Semantics_L1.v:412`** — `Admitted.` (related). -* **`formal/Semantics_L1.v:520`** — `Admitted.` (related). -* **`formal/Semantics_L1.v:608`** — `Axiom region_liveness_at_split_l1`. - Documented in-file as universally false; the residual obstacle is - T_Region_Active_L1 with binder=rv. Three closure paths documented: - (i) restate with `no_region_active_pop_of rv e` side condition, - (ii) multi-set `region_env`, (iii) weaker contextual signature. - L1.G follow-up converts this to a Lemma + 1 narrow admit (per - PR #178 / its main re-port). -* **`formal/Semantics_L1.v:632`** — `Admitted.` capping - `preservation_l1` (related to L1.G's gap above; L2-gated per - §5.1 — closes once L2 Phase 2's effect-typed `TFun` lands). -* **`formal/Semantics_L1.v:703`** — `admit.` inside `preservation_l1` - for one of the lambda-rigidity admits (S_App_Step2 / S_Pair_Step2 - per §4.8 + §5.1). -* **`formal/Semantics_L1.v:704`** — `Admitted.` capping - `preservation_l1` (same lambda-rigidity gap). - -All are tracked in the L1 closure plan and sequenced under the -four-layer redesign. None are soundness-relevant in the legacy -`Semantics.v`; they live entirely on the L1 redesign side. - -=== `formal/Echo.v` — L3 layer K dependency CLOSED 2026-05-27 - -PR #173 (design branch) / its main re-port discharged the -`Eqdep.Eq_rect_eq` (K / UIP) dependency in `mode_le_trans` + -`degrade_mode_comp` via the motive-trick K-free template from -`formal/Modality.v`. **All Echo.v lemmas are now closed under the -global context** — zero K, zero UIP, zero axioms. Entry kept here -for historical visibility; no current escape hatch in `Echo.v`. - -== What this repo does *not* establish today - -Per `formal/PRESERVATION-DESIGN.md` §12.15.4, even after the in-flight -L1 work lands, the following remain open and are tracked here so -downstream consumers do not overclaim: - -* The `ephapax-affine` sublanguage's mechanisation in `formal/` - exists (post-#176: `has_type_l1` carries the modality parameter, - `linear_to_affine : has_type_l1 Linear ... → has_type_l1 Affine ...` - is Qed, mode-specific T_Lam_L1_*, T_Case_L1_*, T_If_L1_* land). - The Rust `AffineChecker` is independent (behavioural tests only); - no mechanised correspondence between the Rust checkers and the - Coq judgment. -* L3 `formal/Echo.v` mechanises the residue type former layer per - PRESERVATION-DESIGN.md §6 (Mode, LEcho, weaken, EchoR + headline - `no_section_collapse_to_residue`). Type-former integration into - Syntax.v / Typing.v / Semantics.v is forward-looking. -* L4 mode-elaboration soundness is a research track, not scheduled. - -Per the policy schema, these are *plan items* rather than (d) DEBT -entries — they have no current escape-hatch site to point at; they -are future judgments that have not yet been written. They are listed -here so that any reader using this document to gauge ephapax -soundness sees the full picture, not just the Admitted-marker count. - -== Cross-references - -* link:{policy}[standards#203] — trusted-base reduction policy - (canonical estate-wide definition) -* link:{handoff}[`formal/PRESERVATION-HANDOFF.md`] — per-PR - diagnostic narrative for the `preservation` closure -* link:{roadmap}[`ROADMAP.adoc` § "Preservation closure plan"] — - canonical 5-phase closure plan -* `formal/PRESERVATION-DESIGN.md` (on branch - `proof/l1-region-threading-design`) — four-layer redesign -* `feedback_ephapax_affine_proofs_not_done` (user auto-memory) — - recurring disambiguation: ephapax-affine ≠ AffineScript, and the - affine side is not mechanised in this repo +Copyright (c) Jonathan D.A. Jewell j.d.a.jewell@open.ac.uk +SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) –> + +== Proof Debt — ephapax (SUPERSEDED — archaeology only) + +____ +=== 🛑 SUPERSEDED + +This document was written against the pre-2026-05-26 legacy-judgment +closure plan and lists `+step_preserves_type+` / +`+step_output_context_eq+` / `+preservation+` as (d) DEBT with a +2026-09-01 deadline. That framing is *banned* per owner directive +2026-05-27: the legacy `+Theorem preservation+` in +`+formal/Semantics.v+` is _provably false_ (`+formal/Counterexample.v+`, +five `+Qed+` lemmas) and its `+Admitted.+` is correct + durable. + +*Canonical proof-debt sources*: - `+PROOF-NEEDS.md+` (root) - +`+formal/PRESERVATION-DESIGN.md §12+` - `+formal/PHASE-D-REDESIGN.md+` + +Retained for traceability against the +https://github.com/hyperpolymath/standards/blob/main/docs/TRUSTED-BASE-REDUCTION-POLICY.adoc[trusted-base-reduction +policy] (standards#203). The classification framework `+(a)–(d)+` is +still meaningful; the specific entries below are stale. +____ + +*Schema*: +https://github.com/hyperpolymath/standards/blob/main/docs/TRUSTED-BASE-REDUCTION-POLICY.adoc[hyperpolymath/standards +`+TRUSTED-BASE-REDUCTION-POLICY.adoc+`] (standards#203). + +Markers in scope (from the 2026-05-26 estate proof-debt audit, +standards#195): - Coq `+Axiom+`, `+Admitted+`, `+admit.+` - Idris2 +`+believe_me+`, `+assert_total+`, top-level `+partial+` - `+TODO PROOF+` +/ `+OWED:+` / `+FIXME PROOF+` markers + +=== (a) DISCHARGED in this repo + +_(None yet — entries move here when their proof lands.)_ + +=== (b) BUDGETED — tested with a refutation budget + +_(None — ephapax’s proof targets are deductive, not property-tested. +Items in this section would belong here if/when we add adversarial-test +budgets for the Rust↔Idris2 ABI boundary code.)_ + +=== (c) NECESSARY AXIOM + +_(None — ephapax does not introduce any irreducible metatheoretic axioms +in the working Coq logic. If future work adds e.g. `+funExt+`, list it +here with a citation to its canonical formalisation.)_ + +=== (d) DEBT — actively to be closed + +==== `+formal/Semantics.v:4924+` — `+Admitted+` inside `+step_preserves_type+` + +* *Lemma*: `+step_preserves_type+` (declared at +`+formal/Semantics.v:3615+`). +* *Statement (informal)*: small-step semantics preserve typing. +* *Owner*: @hyperpolymath +* *Plan*: discharge as part of +https://github.com/hyperpolymath/standards/blob/main/docs/audits/2026-05-26-estate-proof-debt.md[project_ephapax_preservation_closure_plan]. +Estimated 6–9 days. ~40 goals split into sub-buckets A/B/C/D; start with +A. +* *Deadline*: 2026-09-01 (per closure plan). + +==== `+formal/Semantics.v:5983+` — `+Admitted+` inside `+step_output_context_eq+` + +* *Lemma*: `+step_output_context_eq+` (declared at +`+formal/Semantics.v:4944+`). +* *Statement (informal)*: output context is preserved across a step +(modulo region marking). +* *Owner*: @hyperpolymath +* *Plan*: discharged as a corollary of the strengthened +`+step_preserves_type+` proof; depends on the preservation closure plan. +* *Deadline*: 2026-09-01. + +==== `+formal/Semantics.v:6572+` — `+Admitted+` inside `+preservation+` + +* *Theorem*: `+preservation+` (declared at `+formal/Semantics.v:5985+`). +* *Statement (informal)*: the main preservation theorem for the ephapax +region-aware operational semantics. +* *Owner*: @hyperpolymath +* *Plan*: this is the top-level theorem the other two Admitteds feed +into. Discharge order: `+step_preserves_type+` first → then +`+step_output_context_eq+` → then `+preservation+` lands automatically +modulo wiring. +* *Deadline*: 2026-09-01. + +=== Soundness-relevant escape hatches in non-Coq code + +The 2026-05-26 audit also surfaced: - 1 `+believe_me+` reference (a +comment in `+src/formal/Ephapax/Formal/RegionLinear.idr:127+` explicitly +stating "`This is a REAL proof — not (), not believe_me, not +assert_total`" — no actual `+believe_me+` use; the comment is +documentation about NOT using it). - 1 `+partial+` (Idris2 totality +waiver) — TBD if real or comment artefact. - 14 `+TODO PROOF+` / +`+OWED:+` markers — these were not located in this repo by the per-repo +scan; they likely cluster around the same Admitteds above. + +When `+scripts/check-trusted-base.sh+` lands, any genuine occurrence of +the above will require either a leading `+TRUSTED:+` / `+AXIOM:+` +annotation or a `+§(d) DEBT+` entry here. + +=== How to update this file + +After each PR that closes one of the entries above: + +[arabic] +. Move the entry from §(d) → §(a) DISCHARGED. +. Update the closure plan referenced. +. Run `+git cliff --output CHANGELOG.md+` to regenerate the changelog +(see https://github.com/hyperpolymath/standards/pull/206[standards#206] +for the canonical workflow). + +=== Companion documents + +* https://github.com/hyperpolymath/standards/pull/195[standards#195] — +estate proof-debt audit +* https://github.com/hyperpolymath/standards/pull/203[standards#203] — +trusted-base reduction policy (the schema this file follows) +* `+project_ephapax_preservation_closure_plan+` (maintainer memory) — +6–9 day discharge plan + +''''' + +🤖 Initial seed by Claude Code, 2026-05-26. diff --git a/docs/proof-debt.md b/docs/proof-debt.md deleted file mode 100644 index 941c7b6a..00000000 --- a/docs/proof-debt.md +++ /dev/null @@ -1,121 +0,0 @@ - -Copyright (c) Jonathan D.A. Jewell -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) ---> - -# Proof Debt — ephapax (SUPERSEDED — archaeology only) - -> ## 🛑 SUPERSEDED -> -> This document was written against the pre-2026-05-26 legacy-judgment -> closure plan and lists `step_preserves_type` / `step_output_context_eq` / -> `preservation` as (d) DEBT with a 2026-09-01 deadline. That framing is -> **banned** per owner directive 2026-05-27: the legacy -> `Theorem preservation` in `formal/Semantics.v` is *provably false* -> (`formal/Counterexample.v`, five `Qed` lemmas) and its `Admitted.` -> is correct + durable. -> -> **Canonical proof-debt sources**: -> - `PROOF-NEEDS.md` (root) -> - `formal/PRESERVATION-DESIGN.md §12` -> - `formal/PHASE-D-REDESIGN.md` -> -> Retained for traceability against the -> [trusted-base-reduction policy](https://github.com/hyperpolymath/standards/blob/main/docs/TRUSTED-BASE-REDUCTION-POLICY.adoc) -> (standards#203). The classification framework `(a)–(d)` is still -> meaningful; the specific entries below are stale. - -**Schema**: [hyperpolymath/standards `TRUSTED-BASE-REDUCTION-POLICY.adoc`](https://github.com/hyperpolymath/standards/blob/main/docs/TRUSTED-BASE-REDUCTION-POLICY.adoc) (standards#203). - -Markers in scope (from the 2026-05-26 estate proof-debt audit, -standards#195): -- Coq `Axiom`, `Admitted`, `admit.` -- Idris2 `believe_me`, `assert_total`, top-level `partial` -- `TODO PROOF` / `OWED:` / `FIXME PROOF` markers - -## (a) DISCHARGED in this repo - -*(None yet — entries move here when their proof lands.)* - -## (b) BUDGETED — tested with a refutation budget - -*(None — ephapax's proof targets are deductive, not property-tested. Items -in this section would belong here if/when we add adversarial-test budgets -for the Rust↔Idris2 ABI boundary code.)* - -## (c) NECESSARY AXIOM - -*(None — ephapax does not introduce any irreducible metatheoretic axioms -in the working Coq logic. If future work adds e.g. `funExt`, list it here -with a citation to its canonical formalisation.)* - -## (d) DEBT — actively to be closed - -### `formal/Semantics.v:4924` — `Admitted` inside `step_preserves_type` - -- **Lemma**: `step_preserves_type` (declared at `formal/Semantics.v:3615`). -- **Statement (informal)**: small-step semantics preserve typing. -- **Owner**: @hyperpolymath -- **Plan**: discharge as part of [project_ephapax_preservation_closure_plan](https://github.com/hyperpolymath/standards/blob/main/docs/audits/2026-05-26-estate-proof-debt.md). Estimated 6–9 days. ~40 goals split into sub-buckets A/B/C/D; start with A. -- **Deadline**: 2026-09-01 (per closure plan). - -### `formal/Semantics.v:5983` — `Admitted` inside `step_output_context_eq` - -- **Lemma**: `step_output_context_eq` (declared at `formal/Semantics.v:4944`). -- **Statement (informal)**: output context is preserved across a step - (modulo region marking). -- **Owner**: @hyperpolymath -- **Plan**: discharged as a corollary of the strengthened - `step_preserves_type` proof; depends on the preservation closure plan. -- **Deadline**: 2026-09-01. - -### `formal/Semantics.v:6572` — `Admitted` inside `preservation` - -- **Theorem**: `preservation` (declared at `formal/Semantics.v:5985`). -- **Statement (informal)**: the main preservation theorem for the ephapax - region-aware operational semantics. -- **Owner**: @hyperpolymath -- **Plan**: this is the top-level theorem the other two Admitteds feed - into. Discharge order: `step_preserves_type` first → then - `step_output_context_eq` → then `preservation` lands automatically - modulo wiring. -- **Deadline**: 2026-09-01. - -## Soundness-relevant escape hatches in non-Coq code - -The 2026-05-26 audit also surfaced: -- 1 `believe_me` reference (a comment in - `src/formal/Ephapax/Formal/RegionLinear.idr:127` explicitly stating - "This is a REAL proof — not (), not believe_me, not assert_total" — - no actual `believe_me` use; the comment is documentation about NOT - using it). -- 1 `partial` (Idris2 totality waiver) — TBD if real or comment artefact. -- 14 `TODO PROOF` / `OWED:` markers — these were not located in this - repo by the per-repo scan; they likely cluster around the same - Admitteds above. - -When `scripts/check-trusted-base.sh` lands, any genuine occurrence of -the above will require either a leading `TRUSTED:` / `AXIOM:` annotation -or a `§(d) DEBT` entry here. - -## How to update this file - -After each PR that closes one of the entries above: - -1. Move the entry from §(d) → §(a) DISCHARGED. -2. Update the closure plan referenced. -3. Run `git cliff --output CHANGELOG.md` to regenerate the changelog - (see [standards#206](https://github.com/hyperpolymath/standards/pull/206) for - the canonical workflow). - -## Companion documents - -- [standards#195](https://github.com/hyperpolymath/standards/pull/195) — estate proof-debt audit -- [standards#203](https://github.com/hyperpolymath/standards/pull/203) — trusted-base reduction policy (the schema this file follows) -- `project_ephapax_preservation_closure_plan` (maintainer memory) — 6–9 day discharge plan - ---- - -🤖 Initial seed by Claude Code, 2026-05-26. diff --git a/docs/reports/audit/audit-2026-04-04.adoc b/docs/reports/audit/audit-2026-04-04.adoc new file mode 100644 index 00000000..5e189c93 --- /dev/null +++ b/docs/reports/audit/audit-2026-04-04.adoc @@ -0,0 +1,131 @@ +== Audit Report: ephapax — 2026-04-04 + +=== Classification: M2 Estate Audit — Wider Tranche + +''''' + +=== Dangerous Patterns + +[width="100%",cols="40%,30%,30%",options="header",] +|=== +|Pattern |Count |Notes +|`+unwrap()+` + `+expect()+` + `+todo!()+` |~206 |Spread across parser, +package manager, WASM modules + +|`+Admitted+` (Coq) |2 |`+subst_preserves_typing+`, `+preservation+` — +KNOWN, reserved for Opus + +|`+sorry+` |0 | + +|`+believe_me+` (Idris2) |0 | +|=== + +*Total Rust: ~206 calls.* MEDIUM-HIGH. Most are in parser and WASM +bridge. The Coq Admitted count (2) is the critical publication blocker. + +*History:* Was 5 Admitted → 3 → 2. Remaining 2 require deep proof work +(substitution lemma) — reserved for Opus session. + +''''' + +=== Test Coverage + +[cols=",",options="header",] +|=== +|Category |Count +|Unit tests (`+#[test]+`) |372 +|Test files |36 +|Integration tests |Present +|E2E tests |Partial +|Property tests |Planned +|Benchmarks |Minimal +|=== + +*CRG Grade: C* (achieved via CRG C blitz 2026-04-04). 372 tests across +36 files is among the strongest in the estate. Includes integration +tests. typell integration confirmed. + +''''' + +=== Template Residue + +[cols=",",options="header",] +|=== +|Type |Count +|TODO/FIXME markers |~3 +|`+{{OWNER}}+`/`+{{REPO}}+` placeholders |0 +|=== + +Clean. Minimal legitimate dev markers. + +''''' + +=== RSR Compliance + +[cols=",",options="header",] +|=== +|File |Present +|`+EXPLAINME.adoc+` |YES +|`+SECURITY.md+` |YES +|`+CONTRIBUTING.md+` |YES +|`+0-AI-MANIFEST.a2ml+` |YES +|=== + +*Full RSR compliance.* + +''''' + +=== Proof Debt + +[width="100%",cols="29%,33%,38%",options="header",] +|=== +|Item |Count |Detail +|Coq: `+Admitted+` |*2* |`+subst_preserves_typing+` (substitution +lemma), `+preservation+` (mechanical once subst done) + +|Coq: `+Qed+` complete |47 |Strong proof coverage otherwise + +|PROOF-NEEDS.md items |Several |Documented in repo +|=== + +*Proof debt: CRITICAL for publication.* The 2 remaining Admitted are the +primary publication blocker. Without these proofs, the linear type +soundness claim in the paper cannot be made honestly. + +''''' + +=== Publication Safety + +[cols=",",options="header",] +|=== +|Item |Status +|ArXiv papers |2 (`+arcvix-code-as-matter.tex+`, + 1 other) +|Overclaiming |None detected +|Test/claim parity |BLOCKED — 2 Admitted in soundness proof +|Formal verification parity |BLOCKED +|=== + +*Publication status: BLOCKED on 2 Admitted.* Papers make formal +correctness claims that depend on: 1. `+subst_preserves_typing+` proof — +core of type soundness 2. `+preservation+` proof — mechanical +consequence of (1) + +Both reserved for Opus session. Once complete: publication-safe assuming +Rust unwrap reduction in parser. + +''''' + +=== Summary + +[cols=",,",options="header",] +|=== +|Dimension |Grade |Notes +|Dangerous patterns |C |206 calls, parser primary concern +|Test coverage |B |372 tests, solid integration coverage +|RSR compliance |A |All 4 required files present +|Proof debt |*D* |2 Admitted block soundness claim +|Publication safety |*D* |Papers blocked on Admitted closure +|=== + +*Overall: NOT publication-safe until 2 Coq Admitted resolved.* Post-Opus +session: upgrade to B/A and publication-safe. diff --git a/docs/reports/audit/audit-2026-04-04.md b/docs/reports/audit/audit-2026-04-04.md deleted file mode 100644 index 236df889..00000000 --- a/docs/reports/audit/audit-2026-04-04.md +++ /dev/null @@ -1,100 +0,0 @@ -# Audit Report: ephapax — 2026-04-04 - -## Classification: M2 Estate Audit — Wider Tranche - ---- - -## Dangerous Patterns - -| Pattern | Count | Notes | -|---------|-------|-------| -| `unwrap()` + `expect()` + `todo!()` | ~206 | Spread across parser, package manager, WASM modules | -| `Admitted` (Coq) | 2 | `subst_preserves_typing`, `preservation` — KNOWN, reserved for Opus | -| `sorry` | 0 | | -| `believe_me` (Idris2) | 0 | | - -**Total Rust: ~206 calls.** MEDIUM-HIGH. Most are in parser and WASM bridge. The Coq Admitted count (2) is the critical publication blocker. - -**History:** Was 5 Admitted → 3 → 2. Remaining 2 require deep proof work (substitution lemma) — reserved for Opus session. - ---- - -## Test Coverage - -| Category | Count | -|----------|-------| -| Unit tests (`#[test]`) | 372 | -| Test files | 36 | -| Integration tests | Present | -| E2E tests | Partial | -| Property tests | Planned | -| Benchmarks | Minimal | - -**CRG Grade: C** (achieved via CRG C blitz 2026-04-04). 372 tests across 36 files is among the strongest in the estate. Includes integration tests. typell integration confirmed. - ---- - -## Template Residue - -| Type | Count | -|------|-------| -| TODO/FIXME markers | ~3 | -| `{{OWNER}}`/`{{REPO}}` placeholders | 0 | - -Clean. Minimal legitimate dev markers. - ---- - -## RSR Compliance - -| File | Present | -|------|---------| -| `EXPLAINME.adoc` | YES | -| `SECURITY.md` | YES | -| `CONTRIBUTING.md` | YES | -| `0-AI-MANIFEST.a2ml` | YES | - -**Full RSR compliance.** - ---- - -## Proof Debt - -| Item | Count | Detail | -|------|-------|--------| -| Coq: `Admitted` | **2** | `subst_preserves_typing` (substitution lemma), `preservation` (mechanical once subst done) | -| Coq: `Qed` complete | 47 | Strong proof coverage otherwise | -| PROOF-NEEDS.md items | Several | Documented in repo | - -**Proof debt: CRITICAL for publication.** The 2 remaining Admitted are the primary publication blocker. Without these proofs, the linear type soundness claim in the paper cannot be made honestly. - ---- - -## Publication Safety - -| Item | Status | -|------|--------| -| ArXiv papers | 2 (`arcvix-code-as-matter.tex`, + 1 other) | -| Overclaiming | None detected | -| Test/claim parity | BLOCKED — 2 Admitted in soundness proof | -| Formal verification parity | BLOCKED | - -**Publication status: BLOCKED on 2 Admitted.** Papers make formal correctness claims that depend on: -1. `subst_preserves_typing` proof — core of type soundness -2. `preservation` proof — mechanical consequence of (1) - -Both reserved for Opus session. Once complete: publication-safe assuming Rust unwrap reduction in parser. - ---- - -## Summary - -| Dimension | Grade | Notes | -|-----------|-------|-------| -| Dangerous patterns | C | 206 calls, parser primary concern | -| Test coverage | B | 372 tests, solid integration coverage | -| RSR compliance | A | All 4 required files present | -| Proof debt | **D** | 2 Admitted block soundness claim | -| Publication safety | **D** | Papers blocked on Admitted closure | - -**Overall: NOT publication-safe until 2 Coq Admitted resolved.** Post-Opus session: upgrade to B/A and publication-safe. diff --git a/docs/reports/audit/audit-2026-05-26-lemma-b-option-2-obstacle.adoc b/docs/reports/audit/audit-2026-05-26-lemma-b-option-2-obstacle.adoc new file mode 100644 index 00000000..aab44fec --- /dev/null +++ b/docs/reports/audit/audit-2026-05-26-lemma-b-option-2-obstacle.adoc @@ -0,0 +1,146 @@ +== Audit Report: ephapax — 2026-05-26 — Lemma B Phase 2 Option 2 obstacle + +=== Classification: Proof closure analysis, follow-up to `+audit-2026-05-26-standards-134-reconciliation.md+` + +''''' + +=== TL;DR + +Pre-flight analysis of `+formal/PRESERVATION-HANDOFF.md+`’s Option 2 +("`inversion on Hstep with structural recursion, ~150 LOC`") for the +shared S_Region_Step admit found a real obstacle in the +sibling-preserving congruence cases. The counterexample is: + +[source,coq] +---- +e = ELet (ERegion r v_inner) e2 (* well-typed at R with In r R *) +e' = ELet v_inner e2 (* after S_Let_Step / S_Region_Exit *) +R' = remove_first r R (* r exited from inside *) +---- + +`+e2+` may syntactically reference `+r+` and was typed at `+R+` (which +had `+r+`). After the step, `+R'+` has no `+r+`. For preservation we +need `+e'+` typed at `+R'+`, but `+e2+`’s `+r+`-references prevent that. + +The handoff doc anticipated this for the `+expr_free_of_region+` +extraction path ("`a `+step_exit_implies_free_of_exited_region+` lemma +would be FALSE for congruence cases`"). However, the doc’s Option 2 +(structural recursion on `+Hstep+`) was framed as orthogonal to the +freedom-extraction path. On closer inspection, the same counterexample +blocks Option 2’s congruence cases — they need to _rebuild_ `+e'+`’s +typing at `+R'+`, and that rebuild requires `+e2+` to typecheck at +`+R'+`, which `+e2+`’s `+r+`-references prevent. + +=== Verification of the counterexample + +`+T_Let+` (`+formal/Typing.v:103+`) has no `+~In r (free_regions T)+` or +`+expr_free_of_region+` premise on the bound expression’s environment: + +[source,coq] +---- +| T_Let : forall R G G' G'' e1 e2 T1 T2, + R; G |- e1 : T1 -| G' -> + R; ctx_extend G' T1 |- e2 : T2 -| (T1, true) :: G'' -> + R; G |- ELet e1 e2 : T2 -| G'' +---- + +So `+ELet (ERegion r v_inner) e2+` with `+e2+` referencing `+r+` is +well-typed at any `+R+` with `+In r R+`. `+S_Region_Exit+` +(`+formal/Semantics.v:216-221+`) has `+expr_free_of_region r v+` only on +the value being lifted out — not on siblings. + +The step rule `+S_Let_Step+` (`+formal/Semantics.v+` similar pattern) +likewise has no freedom-on-siblings premise. + +So the counterexample is syntactically and semantically constructible +within the language as defined. + +=== Implication for the three resolution paths + +[width="100%",cols="14%,46%,40%",options="header",] +|=== +|Path |Original assessment |After this audit +|1. Simultaneous mutual induction w/ preservation |"`significant +restructuring touching both proofs`" |Still the most robust; carries the +cost of touching `+step_preserves_type+` + `+step_output_context_eq+` + +`+preservation+` together + +|2. Inversion + structural recursion on `+Hstep+` (~150 LOC) +|"`orthogonal to the current case split`" |*Blocked* — congruence cases +need to rebuild typings whose siblings may reference the exited region + +|3. Type-preservation-under-step sub-lemma |Already implemented as +`+step_preserves_type+`; it has the same admit |Same blocker — not a new +path +|=== + +Two further options the handoff doc didn’t enumerate: + +[width="100%",cols="24%,52%,24%",options="header",] +|=== +|Path |Description |Cost +|4. Add typing invariant for siblings |E.g., extend `+T_Let+` (and other +compound-typing rules) with `+~In r (free_regions e2)+` whenever the +bound expression is region-exiting |Language change; would invalidate +prior typed programs; potentially affects every congruence rule + +|5. Add `+expr_free_of_region+` premises to `+S_*_Step+` rules |Make +sibling-freedom a step precondition rather than a typing precondition +|Language change at the step level; cleaner than (4) but still +language-altering +|=== + +=== Recommendation + +Take Path 1 (mutual induction). Path 2’s `+~150 LOC+` estimate was keyed +off the freedom-extraction worry alone; the actual obstacle is more +structural and the structural recursion cannot work in isolation without +one of paths 4/5 (which are language changes). + +Path 1’s structure: + +[source,coq] +---- +Section Preservation. + + Lemma step_preserves_type : ... (* with mutual IH *) + with step_output_context_eq : ... (* aka Lemma B *) + with preservation : ... + + Proof. + (* simultaneous induction on Hstep, sharing congruence-case + reconstruction across all three theorems *) + Qed Qed Qed. + +End Preservation. +---- + +Realistic wall-clock estimate revision (relative to the 2026-05-26 +`+4-6h+` figure from +`+audit-2026-05-26-standards-134-reconciliation.md+`): + +* *Path 1 (mutual induction)*: 8-12 hours wall-clock. The restructuring +is invasive but each of the three lemmas already has its cases mostly +closed — the merge is mostly bookkeeping + shared-IH plumbing. +* *Path 4 or 5 (language change)*: 12-20 hours. Language change cascades +through `+Typing.v+`, possibly `+Syntax.v+`, every backend’s AST +handler, plus re-verification of all previously-closed typing +invariants. Outside the standards#134 scope. + +=== What this DOES NOT change + +* The 4/5 sub-tasks of standards#134 that closed pre-2026-05-26 remain +closed (Idris2 partials, ABI seam, stance doc — all unaffected). +* The 12 cascading goals in `+preservation+` still depend on Lemma B +closing; they’re not independently hard. +* The case-count revision in `+formal/PRESERVATION-HANDOFF.md+` (1+1+12 +open obligations, with 1 mirrored structural admit as the independent +variable) remains correct. + +=== Action + +`+lemma-b-phase2+` branch retains the worktree + the two doc commits +(audit reconciliation + handoff case-count correction). This audit adds +a third doc commit; no proof code committed in this session because the +chosen path (Option 2) does not work and Option 1 needs its own focused +multi-hour session. diff --git a/docs/reports/audit/audit-2026-05-26-lemma-b-option-2-obstacle.md b/docs/reports/audit/audit-2026-05-26-lemma-b-option-2-obstacle.md deleted file mode 100644 index 2660c888..00000000 --- a/docs/reports/audit/audit-2026-05-26-lemma-b-option-2-obstacle.md +++ /dev/null @@ -1,147 +0,0 @@ - - - - - -# Audit Report: ephapax — 2026-05-26 — Lemma B Phase 2 Option 2 obstacle - -## Classification: Proof closure analysis, follow-up to `audit-2026-05-26-standards-134-reconciliation.md` - ---- - -## TL;DR - -Pre-flight analysis of `formal/PRESERVATION-HANDOFF.md`'s Option 2 -("inversion on Hstep with structural recursion, ~150 LOC") for the -shared S_Region_Step admit found a real obstacle in the -sibling-preserving congruence cases. The counterexample is: - -```coq -e = ELet (ERegion r v_inner) e2 (* well-typed at R with In r R *) -e' = ELet v_inner e2 (* after S_Let_Step / S_Region_Exit *) -R' = remove_first r R (* r exited from inside *) -``` - -`e2` may syntactically reference `r` and was typed at `R` (which had `r`). -After the step, `R'` has no `r`. For preservation we need `e'` typed at -`R'`, but `e2`'s `r`-references prevent that. - -The handoff doc anticipated this for the `expr_free_of_region` extraction -path ("a `step_exit_implies_free_of_exited_region` lemma would be FALSE -for congruence cases"). However, the doc's Option 2 (structural recursion -on `Hstep`) was framed as orthogonal to the freedom-extraction path. On -closer inspection, the same counterexample blocks Option 2's congruence -cases — they need to *rebuild* `e'`'s typing at `R'`, and that rebuild -requires `e2` to typecheck at `R'`, which `e2`'s `r`-references prevent. - -## Verification of the counterexample - -`T_Let` (`formal/Typing.v:103`) has no `~In r (free_regions T)` or -`expr_free_of_region` premise on the bound expression's environment: - -```coq -| T_Let : forall R G G' G'' e1 e2 T1 T2, - R; G |- e1 : T1 -| G' -> - R; ctx_extend G' T1 |- e2 : T2 -| (T1, true) :: G'' -> - R; G |- ELet e1 e2 : T2 -| G'' -``` - -So `ELet (ERegion r v_inner) e2` with `e2` referencing `r` is well-typed -at any `R` with `In r R`. `S_Region_Exit` -(`formal/Semantics.v:216-221`) has `expr_free_of_region r v` only on the -value being lifted out — not on siblings. - -The step rule `S_Let_Step` (`formal/Semantics.v` similar pattern) likewise -has no freedom-on-siblings premise. - -So the counterexample is syntactically and semantically constructible -within the language as defined. - -## Implication for the three resolution paths - -| Path | Original assessment | After this audit | -|------|---------------------|------------------| -| 1. Simultaneous mutual induction w/ preservation | "significant restructuring touching both proofs" | Still the most robust; carries the cost of touching `step_preserves_type` + `step_output_context_eq` + `preservation` together | -| 2. Inversion + structural recursion on `Hstep` (~150 LOC) | "orthogonal to the current case split" | **Blocked** — congruence cases need to rebuild typings whose siblings may reference the exited region | -| 3. Type-preservation-under-step sub-lemma | Already implemented as `step_preserves_type`; it has the same admit | Same blocker — not a new path | - -Two further options the handoff doc didn't enumerate: - -| Path | Description | Cost | -|------|-------------|------| -| 4. Add typing invariant for siblings | E.g., extend `T_Let` (and other compound-typing rules) with `~In r (free_regions e2)` whenever the bound expression is region-exiting | Language change; would invalidate prior typed programs; potentially affects every congruence rule | -| 5. Add `expr_free_of_region` premises to `S_*_Step` rules | Make sibling-freedom a step precondition rather than a typing precondition | Language change at the step level; cleaner than (4) but still language-altering | - -## Recommendation - -Take Path 1 (mutual induction). Path 2's `~150 LOC` estimate was -keyed off the freedom-extraction worry alone; the actual obstacle is -more structural and the structural recursion cannot work in isolation -without one of paths 4/5 (which are language changes). - -Path 1's structure: - -```coq -Section Preservation. - - Lemma step_preserves_type : ... (* with mutual IH *) - with step_output_context_eq : ... (* aka Lemma B *) - with preservation : ... - - Proof. - (* simultaneous induction on Hstep, sharing congruence-case - reconstruction across all three theorems *) - Qed Qed Qed. - -End Preservation. -``` - -Realistic wall-clock estimate revision (relative to the -2026-05-26 `4-6h` figure from `audit-2026-05-26-standards-134-reconciliation.md`): - -- **Path 1 (mutual induction)**: 8-12 hours wall-clock. The - restructuring is invasive but each of the three lemmas already has - its cases mostly closed — the merge is mostly bookkeeping + - shared-IH plumbing. -- **Path 4 or 5 (language change)**: 12-20 hours. Language change - cascades through `Typing.v`, possibly `Syntax.v`, every backend's - AST handler, plus re-verification of all previously-closed typing - invariants. Outside the standards#134 scope. - -## What this DOES NOT change - -- The 4/5 sub-tasks of standards#134 that closed pre-2026-05-26 - remain closed (Idris2 partials, ABI seam, stance doc — all - unaffected). -- The 12 cascading goals in `preservation` still depend on Lemma B - closing; they're not independently hard. -- The case-count revision in `formal/PRESERVATION-HANDOFF.md` (1+1+12 - open obligations, with 1 mirrored structural admit as the - independent variable) remains correct. - -## Action - -`lemma-b-phase2` branch retains the worktree + the two doc commits -(audit reconciliation + handoff case-count correction). This audit -adds a third doc commit; no proof code committed in this session -because the chosen path (Option 2) does not work and Option 1 needs -its own focused multi-hour session. diff --git a/docs/reports/audit/audit-2026-05-26-standards-134-reconciliation.adoc b/docs/reports/audit/audit-2026-05-26-standards-134-reconciliation.adoc new file mode 100644 index 00000000..7379f858 --- /dev/null +++ b/docs/reports/audit/audit-2026-05-26-standards-134-reconciliation.adoc @@ -0,0 +1,118 @@ +== Audit Report: ephapax — 2026-05-26 — standards#134 reconciliation + +=== Classification: Estate proof-debt epic, P2 sub-issue (standards#124 / standards#134) + +''''' + +=== Context + +`+hyperpolymath/standards#134+` (sub-issue of `+standards#124+`, the +estate proof-debt epic) opened with the following ephapax line item from +the 2026-05-18 reconciled audit: + +____ +*ephapax* — 11 partial; also Rust/SPARK NON-COMPLIANT (no seam) 35k-LOC +compiler: 11 partial in Idris2; bare Rust, no seam, 49 unsafe. Tighten +totality + add ABI seam for type-checker invariants + stance doc. +____ + +Five sub-tasks, all P2. As of 2026-05-26 only the Coq item is still +load-bearing. + +=== Sub-task ledger (verified by re-grep against `+main+` 2026-05-26) + +[width="99%",cols="7%,16%,32%,32%,13%",options="header",] +|=== +|# |Sub-task |Audit (2026-05-18) |Now (2026-05-26) |Status +|1 |Idris2 `+partial+` count |11 (across 9 files) |*0* |✅ DONE — May-20 +totality sub-campaign, PRs #88-#100 + +|2 |ABI seam (Rust↔Idris2 invariants) |none (NON-COMPLIANT) +|`+src/abi/Ephapax/ABI/{Types,Foreign,Invariants}.idr+` shipped + +`+abi-verify+` CI gate |✅ DONE — PR #95 + +|3 |`+RUST-SPARK-STANCE.adoc+` |missing |182 lines |✅ DONE — landed +alongside PR #95 + +|4 |Coq `+preservation+` Qed |910 open goals; in-file false "`Qed +2026-04-27`" claim |`+Admitted+` with *1 explicit admit in +`+step_output_context_eq+` (Lemma B) + 1 in `+step_preserves_type+` + 12 +cascading congruence goals in `+preservation+`* |⚠️ Last open work item +— see `+formal/PRESERVATION-HANDOFF.md+` + +|5 |Rust `+unsafe+` blocks |49 |*30* (14 in +`+src/ephapax-runtime/src/lib.rs+`, 12 in `+…/list.rs+`, 4 in +`+src/ephapax-interp/src/lib.rs+`) |🟡 Partial — concentrated at +FFI/runtime boundary; policy documented in `+RUST-SPARK-STANCE.adoc+`; +separate audit out of scope for standards#134 +|=== + +=== Why the original case-count for sub-task 4 was misleading + +The audit’s "`910 open goals`" framing reflected the state of the proof +script _before_ the 2026-05-20 / 2026-05-24 sessions: + +* `+910 → 29+` via `+remember (cfg) ...; induction Hstep+` (PR #102) +* `+29 → 22+` via universal-IH revert (PR #106) +* `+22 → 12+` via per-case manual closures (PR #116) +* Lemma B `+step_output_context_eq+` scaffolded with `+cfg+`-remember +pattern; *27 of 35 step rules closed* (PR #121/#124/#126). The 24 listed +as "`open`" in the 2026-05-21 PROOF-NEEDS.md baseline were superseded by +the 2026-05-24 cluster closures (handoff doc § "`Cluster A — FULLY +CLOSED`", § "`Cluster C — FULLY CLOSED`"). +* `+step_preserves_type+` (the type-only sub-lemma) likewise closed all +but *one* sub-case. + +Empirical verification (this audit, `+coqc 8.18.0+`): + +.... +==STPT-OPEN== 1 goal +==LB-OPEN== 1 goal +==PRES-OPEN== 12 goals +.... + +The 12 in `+preservation+` are the cascading congruence cases +(`+S_*_Step+`, `+S_StringConcat_Step1/2+`, `+S_StringLen_Step+`, +`+S_Region_Step+`) — they close mechanically once Lemma B’s body is +`+Qed+` (`+~2h fan-out+`, Phase 2 of PROOF-NEEDS.md). They are NOT +independently hard. + +So the actual closure surface is *one structural admit* appearing twice +(`+Semantics.v:4885+`, `+Semantics.v:5963+`) — the S_Region_Step +`+r = r1+` sub-case where an inner step exits the outer region from +inside. The doc characterises this as type-alignment circularity; +resolution path agreed (2026-05-26): *Option 2 — +`+exit_implies_typing_at_remove_first+` helper lemma proved by +structural recursion on `+Hstep+`*. + +=== Wall-clock estimate revision + +PROOF-NEEDS.md / PRESERVATION-HANDOFF.md previously cited "`8-15 focused +hours for Lemma B alone, + ~2h cascade`". Those numbers were keyed off +the "`31 cases`" framing. After the 2026-05-24 cluster closures the +actual remaining surface is one helper lemma (~150 LOC by the doc’s own +estimate) plus the cascade. + +*Revised estimate: 4-6 hours wall-clock* for the whole chain: - ~3h +Option 2 helper lemma body - ~1h plug into `+step_preserves_type+` + +Lemma B admits - ~2h cascade through `+preservation+`’s 12 congruence +goals + S_Region_Step - ~1h unwind checklist (PROOF-NEEDS / ROADMAP / +RUST-SPARK-STANCE / delete HANDOFF / `+Admitted → Qed+`) + +=== What this audit does NOT discharge + +* *Sub-task 5* (30 remaining `+unsafe+` blocks). FFI-boundary code with +policy stance documented but no per-block contract review. Worth filing +as a separate SPARK-style contract-audit ticket; outside the +standards#134 scope as originally framed. +* *Pre-v1.0.0 line items* in PROOF-NEEDS.md ("`Linear type +consumption`", "`Effect system soundness`", "`Region safety`", +"`Compiler correctness`") — these are roadmap targets, not standards#134 +sub-tasks. + +=== Joint-close criteria + +PRs that close the Lemma B admit + cascade `+preservation+` to `+Qed+` +should land with `+Refs hyperpolymath/standards#134+` per the epic’s +joint-close convention. Joint-close only on explicit agreement from the +standards#124 maintainer. diff --git a/docs/reports/audit/audit-2026-05-26-standards-134-reconciliation.md b/docs/reports/audit/audit-2026-05-26-standards-134-reconciliation.md deleted file mode 100644 index 39f44829..00000000 --- a/docs/reports/audit/audit-2026-05-26-standards-134-reconciliation.md +++ /dev/null @@ -1,114 +0,0 @@ - - - - - -# Audit Report: ephapax — 2026-05-26 — standards#134 reconciliation - -## Classification: Estate proof-debt epic, P2 sub-issue (standards#124 / standards#134) - ---- - -## Context - -`hyperpolymath/standards#134` (sub-issue of `standards#124`, the estate proof-debt -epic) opened with the following ephapax line item from the 2026-05-18 reconciled -audit: - -> **ephapax** — 11 partial; also Rust/SPARK NON-COMPLIANT (no seam) -> 35k-LOC compiler: 11 partial in Idris2; bare Rust, no seam, 49 unsafe. -> Tighten totality + add ABI seam for type-checker invariants + stance doc. - -Five sub-tasks, all P2. As of 2026-05-26 only the Coq item is still load-bearing. - -## Sub-task ledger (verified by re-grep against `main` 2026-05-26) - -| # | Sub-task | Audit (2026-05-18) | Now (2026-05-26) | Status | -|---|----------|--------------------|--------------------|--------| -| 1 | Idris2 `partial` count | 11 (across 9 files) | **0** | ✅ DONE — May-20 totality sub-campaign, PRs #88-#100 | -| 2 | ABI seam (Rust↔Idris2 invariants) | none (NON-COMPLIANT) | `src/abi/Ephapax/ABI/{Types,Foreign,Invariants}.idr` shipped + `abi-verify` CI gate | ✅ DONE — PR #95 | -| 3 | `RUST-SPARK-STANCE.adoc` | missing | 182 lines | ✅ DONE — landed alongside PR #95 | -| 4 | Coq `preservation` Qed | 910 open goals; in-file false "Qed 2026-04-27" claim | `Admitted` with **1 explicit admit in `step_output_context_eq` (Lemma B) + 1 in `step_preserves_type` + 12 cascading congruence goals in `preservation`** | ⚠️ Last open work item — see `formal/PRESERVATION-HANDOFF.md` | -| 5 | Rust `unsafe` blocks | 49 | **30** (14 in `src/ephapax-runtime/src/lib.rs`, 12 in `…/list.rs`, 4 in `src/ephapax-interp/src/lib.rs`) | 🟡 Partial — concentrated at FFI/runtime boundary; policy documented in `RUST-SPARK-STANCE.adoc`; separate audit out of scope for standards#134 | - -## Why the original case-count for sub-task 4 was misleading - -The audit's "910 open goals" framing reflected the state of the proof script -*before* the 2026-05-20 / 2026-05-24 sessions: - -- `910 → 29` via `remember (cfg) ...; induction Hstep` (PR #102) -- `29 → 22` via universal-IH revert (PR #106) -- `22 → 12` via per-case manual closures (PR #116) -- Lemma B `step_output_context_eq` scaffolded with `cfg`-remember pattern; **27 of 35 step rules closed** (PR #121/#124/#126). The 24 listed as "open" in the 2026-05-21 PROOF-NEEDS.md baseline were superseded by the 2026-05-24 cluster closures (handoff doc § "Cluster A — FULLY CLOSED", § "Cluster C — FULLY CLOSED"). -- `step_preserves_type` (the type-only sub-lemma) likewise closed all but **one** sub-case. - -Empirical verification (this audit, `coqc 8.18.0`): - -``` -==STPT-OPEN== 1 goal -==LB-OPEN== 1 goal -==PRES-OPEN== 12 goals -``` - -The 12 in `preservation` are the cascading congruence cases (`S_*_Step`, -`S_StringConcat_Step1/2`, `S_StringLen_Step`, `S_Region_Step`) — they close -mechanically once Lemma B's body is `Qed` (`~2h fan-out`, Phase 2 of -PROOF-NEEDS.md). They are NOT independently hard. - -So the actual closure surface is **one structural admit** appearing twice -(`Semantics.v:4885`, `Semantics.v:5963`) — the S_Region_Step `r = r1` -sub-case where an inner step exits the outer region from inside. The doc -characterises this as type-alignment circularity; resolution path agreed -(2026-05-26): **Option 2 — `exit_implies_typing_at_remove_first` helper -lemma proved by structural recursion on `Hstep`**. - -## Wall-clock estimate revision - -PROOF-NEEDS.md / PRESERVATION-HANDOFF.md previously cited "8-15 focused -hours for Lemma B alone, + ~2h cascade". Those numbers were keyed off the -"31 cases" framing. After the 2026-05-24 cluster closures the actual -remaining surface is one helper lemma (~150 LOC by the doc's own estimate) -plus the cascade. - -**Revised estimate: 4-6 hours wall-clock** for the whole chain: -- ~3h Option 2 helper lemma body -- ~1h plug into `step_preserves_type` + Lemma B admits -- ~2h cascade through `preservation`'s 12 congruence goals + S_Region_Step -- ~1h unwind checklist (PROOF-NEEDS / ROADMAP / RUST-SPARK-STANCE / delete - HANDOFF / `Admitted → Qed`) - -## What this audit does NOT discharge - -- **Sub-task 5** (30 remaining `unsafe` blocks). FFI-boundary code with - policy stance documented but no per-block contract review. Worth filing - as a separate SPARK-style contract-audit ticket; outside the standards#134 - scope as originally framed. -- **Pre-v1.0.0 line items** in PROOF-NEEDS.md ("Linear type consumption", - "Effect system soundness", "Region safety", "Compiler correctness") — - these are roadmap targets, not standards#134 sub-tasks. - -## Joint-close criteria - -PRs that close the Lemma B admit + cascade `preservation` to `Qed` should -land with `Refs hyperpolymath/standards#134` per the epic's joint-close -convention. Joint-close only on explicit agreement from the standards#124 -maintainer. diff --git a/docs/tech-debt-2026-05-26.adoc b/docs/tech-debt-2026-05-26.adoc new file mode 100644 index 00000000..6e5fd2c9 --- /dev/null +++ b/docs/tech-debt-2026-05-26.adoc @@ -0,0 +1,92 @@ +SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) –> + +== Tech-Debt Audit — ephapax — 2026-05-26 + +*Source:* estate-wide automated scan 2026-05-26. *Companion:* +https://github.com/hyperpolymath/standards/tree/main/docs/audits[`+hyperpolymath/standards+` +2026-05-26-estate-*-debt audits]. *Combined severity:* `+HIGH+`. + +This file records the _raw findings_ — it does not by itself fix the +debt. Each section ends with a '`Recommended next move`' line; closing +the debt is follow-up work. + +=== 1. Proof debt + +Scanner counted the following markers in proof-bearing files of this +repo: + +.... +files= 23 | Coq-Axm/Adm= 3 | Lean-srry/ax= 0 | Agda-pst= 0 | Idr-blv= 1 | Idr-prtl= 1 | Fstr-asm= 0 | TODO= 14 | Unsafe= 0 +.... + +*Total markers:* 19. *Severity:* `+>019+`. + +*Marker types* (any non-zero counts above): - Coq `+Axiom+`/`+Admitted+` +— unconditional proof escapes. - Lean `+sorry+`/`+axiom+` — Lean’s +equivalent. - Agda `+postulate+` — accepted axiomatically. - Idris2 +`+believe_me+`/`+assert_total+` — runtime-safe coercion / totality +assumption. - Idris2 top-level `+partial+` — totality-check waived. - F* +`+assume val+`/`+admit_p+` — F* admit. - `+TODO PROOF+` / `+OWED:+` — +self-documented debt markers. - `+unsafePerformIO+`/`+unsafeCoerce+` — +soundness-relevant escape hatches in Haskell/Rust source. + +*Recommended next move:* triage each finding into one of: (a) discharge +by proof, (b) cover with property-tests + a documented refutation +budget, or (c) annotate as a known/necessary axiom (e.g. `+funExt+`) in +`+docs/proof-debt.md+`. + +=== 2. Licence debt + +[cols=",",options="header",] +|=== +|Field |Value +|LICENSE file |`+LICENSE+` +|SPDX header |`+MPL-2.0+` +|Manifest licence |`+PMPL-1.0-or-later+` +|Body classifier |`+PMPL-1.0-or-later+` +|Severity |`+HIGH-mismatch+` +|Notes |`+SPDX=MPL-2.0_but_body=PMPL+` +|=== + +*Recommended next move:* the SPDX header says `+MPL-2.0+` but the +licence body still uses Palimpsest/PMPL language. Per estate policy +(memory: `+feedback_estate_lang_policy_2026_05_25+`) the migration +target is MPL-2.0. Replace the LICENSE body with the canonical MPL-2.0 +text (keep SPDX header; can keep a small Palimpsest preamble as +commentary). + +=== 3. Documentation debt + +[cols=",",options="header",] +|=== +|Field |Value +|README lines |507 +|`+docs/+` files |9 +|`+docs/+` LoC |1965 +|CHANGELOG.md |Y +|CONTRIBUTING.md |Y +|CODE_OF_CONDUCT.md |Y +|SECURITY.md |Y +|Severity |`+MEDIUM+` +|=== + +*Recommended next move:* introduce a `+docs/+` directory. The README at +507 lines has likely grown to do the work of `+docs/+` — split it into a +thin README + `+docs/architecture.md+`, `+docs/usage.md+`, etc. +Heavy-wiki exemplars to copy from: `+affinescript+`, `+boj-server+`, +`+echidna+`, `+hypatia+`. + +=== Cross-references + +* Estate proof-debt audit: +`+hyperpolymath/standards/docs/audits/2026-05-26-estate-proof-debt.md+` +* Estate licence-debt audit: +`+hyperpolymath/standards/docs/audits/2026-05-26-estate-licence-debt.md+` +* Estate documentation-debt audit: +`+hyperpolymath/standards/docs/audits/2026-05-26-estate-documentation-debt.md+` + +''''' + +🤖 Generated by Claude Code estate-wide tech-debt scan (2026-05-26). +This file is informational — closing the debt is follow-up work owned by +the maintainer. diff --git a/docs/tech-debt-2026-05-26.md b/docs/tech-debt-2026-05-26.md deleted file mode 100644 index a7b03b19..00000000 --- a/docs/tech-debt-2026-05-26.md +++ /dev/null @@ -1,73 +0,0 @@ - -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) ---> - -# Tech-Debt Audit — ephapax — 2026-05-26 - -**Source:** estate-wide automated scan 2026-05-26. -**Companion:** [`hyperpolymath/standards` 2026-05-26-estate-*-debt audits](https://github.com/hyperpolymath/standards/tree/main/docs/audits). -**Combined severity:** `HIGH`. - -This file records the *raw findings* — it does not by itself fix the debt. Each section ends with a 'Recommended next move' line; closing the debt is follow-up work. - -## 1. Proof debt - -Scanner counted the following markers in proof-bearing files of this repo: - -``` -files= 23 | Coq-Axm/Adm= 3 | Lean-srry/ax= 0 | Agda-pst= 0 | Idr-blv= 1 | Idr-prtl= 1 | Fstr-asm= 0 | TODO= 14 | Unsafe= 0 -``` - -**Total markers:** 19. **Severity:** `>019`. - -**Marker types** (any non-zero counts above): -- Coq `Axiom`/`Admitted` — unconditional proof escapes. -- Lean `sorry`/`axiom` — Lean's equivalent. -- Agda `postulate` — accepted axiomatically. -- Idris2 `believe_me`/`assert_total` — runtime-safe coercion / totality assumption. -- Idris2 top-level `partial` — totality-check waived. -- F\* `assume val`/`admit_p` — F\* admit. -- `TODO PROOF` / `OWED:` — self-documented debt markers. -- `unsafePerformIO`/`unsafeCoerce` — soundness-relevant escape hatches in Haskell/Rust source. - -**Recommended next move:** triage each finding into one of: (a) discharge by proof, (b) cover with property-tests + a documented refutation budget, or (c) annotate as a known/necessary axiom (e.g. `funExt`) in `docs/proof-debt.md`. - -## 2. Licence debt - -| Field | Value | -|---|---| -| LICENSE file | `LICENSE` | -| SPDX header | `MPL-2.0` | -| Manifest licence | `PMPL-1.0-or-later` | -| Body classifier | `PMPL-1.0-or-later` | -| Severity | `HIGH-mismatch` | -| Notes | `SPDX=MPL-2.0_but_body=PMPL` | - -**Recommended next move:** the SPDX header says `MPL-2.0` but the licence body still uses Palimpsest/PMPL language. Per estate policy (memory: `feedback_estate_lang_policy_2026_05_25`) the migration target is MPL-2.0. Replace the LICENSE body with the canonical MPL-2.0 text (keep SPDX header; can keep a small Palimpsest preamble as commentary). - -## 3. Documentation debt - -| Field | Value | -|---|---| -| README lines | 507 | -| `docs/` files | 9 | -| `docs/` LoC | 1965 | -| CHANGELOG.md | Y | -| CONTRIBUTING.md | Y | -| CODE_OF_CONDUCT.md | Y | -| SECURITY.md | Y | -| Severity | `MEDIUM` | - -**Recommended next move:** introduce a `docs/` directory. The README at 507 lines has likely grown to do the work of `docs/` — split it into a thin README + `docs/architecture.md`, `docs/usage.md`, etc. Heavy-wiki exemplars to copy from: `affinescript`, `boj-server`, `echidna`, `hypatia`. - -## Cross-references - -- Estate proof-debt audit: `hyperpolymath/standards/docs/audits/2026-05-26-estate-proof-debt.md` -- Estate licence-debt audit: `hyperpolymath/standards/docs/audits/2026-05-26-estate-licence-debt.md` -- Estate documentation-debt audit: `hyperpolymath/standards/docs/audits/2026-05-26-estate-documentation-debt.md` - ---- - -🤖 Generated by Claude Code estate-wide tech-debt scan (2026-05-26). This file is informational — closing the debt is follow-up work owned by the maintainer. diff --git a/editors/tree-sitter-ephapax/README.adoc b/editors/tree-sitter-ephapax/README.adoc new file mode 100644 index 00000000..47c9c1b2 --- /dev/null +++ b/editors/tree-sitter-ephapax/README.adoc @@ -0,0 +1,26 @@ +== tree-sitter-ephapax + +Tree-sitter grammar for +https://github.com/hyperpolymath/ephapax[Ephapax] — a dyadic language +with affine and linear types. + +=== Usage + +[source,bash] +---- +npm install +npx tree-sitter generate +npx tree-sitter test +---- + +=== Features + +* Full syntax support: `+let+`, `+let!+`, `+region+`, `+@r+` annotations +* Highlights distinguish linear (`+let!+`) from affine (`+let+`) +bindings +* Region annotations (`+@r+`) highlighted as labels +* Local variable tracking for scope-aware navigation + +=== File Extension + +`+.eph+` diff --git a/editors/tree-sitter-ephapax/README.md b/editors/tree-sitter-ephapax/README.md deleted file mode 100644 index b930d1b7..00000000 --- a/editors/tree-sitter-ephapax/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# tree-sitter-ephapax - - - - -Tree-sitter grammar for [Ephapax](https://github.com/hyperpolymath/ephapax) — a dyadic language with affine and linear types. - -## Usage - -```bash -npm install -npx tree-sitter generate -npx tree-sitter test -``` - -## Features - -- Full syntax support: `let`, `let!`, `region`, `@r` annotations -- Highlights distinguish linear (`let!`) from affine (`let`) bindings -- Region annotations (`@r`) highlighted as labels -- Local variable tracking for scope-aware navigation - -## File Extension - -`.eph` diff --git a/ephapax-linear/README.adoc b/ephapax-linear/README.adoc new file mode 100644 index 00000000..592dc9a0 --- /dev/null +++ b/ephapax-linear/README.adoc @@ -0,0 +1,86 @@ +== ephapax-linear + +____ +=== 🛑 First-read disambiguation + +This crate is part of *`+hyperpolymath/ephapax+`* — a research language +for compile-time WebAssembly memory safety, formally verified in Coq + +Idris2. + +*`+ephapax-linear+` is _not_ `+hyperpolymath/affinescript+`.* +AffineScript is a separate, unrelated language (JS/TS/AffineScript +successor, OCaml + AffineScript runtime). The two share only the compile +target (`+hyperpolymath/typed-wasm+`). + +*Internal naming trap (important):* This crate implements _both_ +sublanguages of Ephapax — `+ephapax-linear+` (strict core) AND +`+ephapax-affine+` (versatile prototyping companion). The name of the +crate is `+ephapax-linear+` for historical reasons, but *the affine +grammar in this crate is NOT `+AffineScript+`.* The lexical overlap of +the word `+affine+` is a coincidence of substructural-logic family +terminology, not a project relationship. Do not apply tactics, lessons, +or framings from `+hyperpolymath/affinescript+` here, and vice versa. + +Canonical disambiguation: +https://github.com/hyperpolymath/nextgen-languages/blob/main/docs/disambiguation/ephapax-vs-affinescript.md[`+hyperpolymath/nextgen-languages/docs/disambiguation/ephapax-vs-affinescript.md+`]. +____ + +Standalone linear/affine discipline checker for the Ephapax language. + +____ +*Naming note.* This crate is called `+ephapax-linear+` for historical +reasons; it implements *both* L2 modalities (Linear and Affine). The two +are not different languages — they are two admissible-derivation regimes +over the same syntax and semantics. See +link:../docs/vision/EPHAPAX-VISION.adoc[`+docs/vision/EPHAPAX-VISION.adoc+`] +for the dyad framing, and +link:../formal/PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md §5+`] +for the L2 layer in the four-layer architecture. +____ + +=== Two L2 Modalities + +This crate implements two *focused substructural grammars* — two views +of the same ephapax syntax, each enforcing a different structural +discipline: + +[cols=",,",options="header",] +|=== +|Property |Linear Grammar |Affine Grammar +|Exchange |Yes |Yes +|Weakening |*No* (must consume) |*Yes* (implicit drop) +|Contraction |*No* (single use) |*No* (single use) +|`+let!+` |Exactly-once |Exactly-once (linear island) +|`+let+` |Unrestricted types only |Any type +|`+drop(e)+` |*Forbidden* |Permitted +|Branch agreement |*Required* |Not required +|Region exit |Must consume |Implicit drop (warns) +|=== + +=== Dyadic Design + +Ephapax is *dyadic* — both disciplines coexist per-program. `+let!+` +always means linear, `+let+` always means affine. There is no global +mode. + +=== Usage + +[source,rust] +---- +use ephapax_linear::{check_expr, Discipline}; + +// Check under linear discipline +check_expr(&expr, Discipline::Linear)?; + +// Check under affine discipline +check_expr(&expr, Discipline::Affine)?; +---- + +=== Grammar Specifications + +* `+grammar/linear.ebnf+` — Linear focused grammar (EBNF) +* `+grammar/affine.ebnf+` — Affine focused grammar (EBNF) + +=== License + +SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/ephapax-linear/README.md b/ephapax-linear/README.md deleted file mode 100644 index ea48b8f6..00000000 --- a/ephapax-linear/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# ephapax-linear - -> ## 🛑 First-read disambiguation -> -> This crate is part of **`hyperpolymath/ephapax`** — a research language for -> compile-time WebAssembly memory safety, formally verified in Coq + Idris2. -> -> **`ephapax-linear` is *not* `hyperpolymath/affinescript`.** AffineScript is -> a separate, unrelated language (JS/TS/AffineScript successor, OCaml + AffineScript -> runtime). The two share only the compile target (`hyperpolymath/typed-wasm`). -> -> **Internal naming trap (important):** This crate implements *both* -> sublanguages of Ephapax — `ephapax-linear` (strict core) AND `ephapax-affine` -> (versatile prototyping companion). The name of the crate is `ephapax-linear` -> for historical reasons, but **the affine grammar in this crate is NOT -> `AffineScript`.** The lexical overlap of the word `affine` is a coincidence -> of substructural-logic family terminology, not a project relationship. Do -> not apply tactics, lessons, or framings from `hyperpolymath/affinescript` -> here, and vice versa. -> -> Canonical disambiguation: -> [`hyperpolymath/nextgen-languages/docs/disambiguation/ephapax-vs-affinescript.md`](https://github.com/hyperpolymath/nextgen-languages/blob/main/docs/disambiguation/ephapax-vs-affinescript.md). - -Standalone linear/affine discipline checker for the Ephapax language. - -> **Naming note.** This crate is called `ephapax-linear` for historical -> reasons; it implements **both** L2 modalities (Linear and Affine). The -> two are not different languages — they are two admissible-derivation -> regimes over the same syntax and semantics. See -> [`docs/vision/EPHAPAX-VISION.adoc`](../docs/vision/EPHAPAX-VISION.adoc) -> for the dyad framing, and -> [`formal/PRESERVATION-DESIGN.md §5`](../formal/PRESERVATION-DESIGN.md) -> for the L2 layer in the four-layer architecture. - -## Two L2 Modalities - -This crate implements two **focused substructural grammars** — two views of the same ephapax syntax, each enforcing a different structural discipline: - -| Property | Linear Grammar | Affine Grammar | -|----------|---------------|----------------| -| Exchange | Yes | Yes | -| Weakening | **No** (must consume) | **Yes** (implicit drop) | -| Contraction | **No** (single use) | **No** (single use) | -| `let!` | Exactly-once | Exactly-once (linear island) | -| `let` | Unrestricted types only | Any type | -| `drop(e)` | **Forbidden** | Permitted | -| Branch agreement | **Required** | Not required | -| Region exit | Must consume | Implicit drop (warns) | - -## Dyadic Design - -Ephapax is **dyadic** — both disciplines coexist per-program. `let!` always means linear, `let` always means affine. There is no global mode. - -## Usage - -```rust -use ephapax_linear::{check_expr, Discipline}; - -// Check under linear discipline -check_expr(&expr, Discipline::Linear)?; - -// Check under affine discipline -check_expr(&expr, Discipline::Affine)?; -``` - -## Grammar Specifications - -- `grammar/linear.ebnf` — Linear focused grammar (EBNF) -- `grammar/affine.ebnf` — Affine focused grammar (EBNF) - -## License - -SPDX-License-Identifier: CC-BY-SA-4.0 - diff --git a/formal/DESIGN-NOTE-2026-03-28-projected-lookups.adoc b/formal/DESIGN-NOTE-2026-03-28-projected-lookups.adoc new file mode 100644 index 00000000..871f6a78 --- /dev/null +++ b/formal/DESIGN-NOTE-2026-03-28-projected-lookups.adoc @@ -0,0 +1,64 @@ +== Design Note: Projected Context Lookups (2026-03-28) + +=== Problem + +Rocq 9.1.1 `+congruence+`, `+discriminate+`, and `+injection+` all fail +on hypotheses of the form `+Some (T1, true) = Some (T0, false)+` inside +complex inductive proofs (24-case typing rule induction). Works in +standalone lemmas. + +Root cause: `+Coq.Strings.String.length+` shadows +`+Coq.Lists.List.length+` across module boundaries. `+Typing.v+` exports +lemmas with `+List.length+` in their types, but `+Semantics.v+` +(importing String) resolves `+length+` to `+String.length+`, making +`+eapply+`/`+eassumption+` fail silently. + +=== Solution + +Added projected accessors to `+Syntax.v+`: + +[source,coq] +---- +Definition ctx_lookup_ty (G : ctx) (i : var) : option ty := ... +Definition ctx_lookup_flag (G : ctx) (i : var) : option bool := ... +---- + +These decompose `+option (ty * bool)+` into simple `+option ty+` and +`+option bool+`, which Rocq can discriminate/inject without issue. + +Key lemma: + +[source,coq] +---- +Lemma ctx_lookup_cons_zero_flag_contra : + forall (T1 : ty) (G'' : ctx) (T0 : ty), + ctx_lookup ((T1, true) :: G'') 0 = Some (T0, false) -> False. +---- + +Uses `+f_equal+` with functional extraction to get +`+Some true = Some false+`, then `+discriminate+` on `+option bool+` +(which works fine). + +=== Impact + +* `+flags_only_increase+`: *Qed* (was primary blocker) +* `+ctx_mark_used_flag_at+`, `+ctx_mark_used_flag_other+`, +`+ctx_mark_used_ty_other+`: all Qed +* T_Let/T_LetLin/T_Case idx=0 cases in `+flags_only_increase+`: closed +via `+ctx_lookup_cons_zero_flag_contra+` + +=== Remaining + +Same projected approach needed for: - `+ctx_mark_used_types_agree+` (1 +trivial case) - `+ctx_mark_used_false_preserved+` (empty ctx case) - +`+typing_ctx_transfer+` T_Let/T_LetLin/T_Lam/T_Case (need consumption +tracking conjunct) + +=== Import Order Fix + +[source,coq] +---- +(* Typing.v and Semantics.v: import String BEFORE List *) +Require Import Coq.Strings.String. (* Before List so List.length wins *) +Require Import Coq.Lists.List. +---- diff --git a/formal/DESIGN-NOTE-2026-03-28-projected-lookups.md b/formal/DESIGN-NOTE-2026-03-28-projected-lookups.md deleted file mode 100644 index 2e614a77..00000000 --- a/formal/DESIGN-NOTE-2026-03-28-projected-lookups.md +++ /dev/null @@ -1,58 +0,0 @@ - - -# Design Note: Projected Context Lookups (2026-03-28) - -## Problem - -Rocq 9.1.1 `congruence`, `discriminate`, and `injection` all fail on -hypotheses of the form `Some (T1, true) = Some (T0, false)` inside complex -inductive proofs (24-case typing rule induction). Works in standalone lemmas. - -Root cause: `Coq.Strings.String.length` shadows `Coq.Lists.List.length` -across module boundaries. `Typing.v` exports lemmas with `List.length` in -their types, but `Semantics.v` (importing String) resolves `length` to -`String.length`, making `eapply`/`eassumption` fail silently. - -## Solution - -Added projected accessors to `Syntax.v`: - -```coq -Definition ctx_lookup_ty (G : ctx) (i : var) : option ty := ... -Definition ctx_lookup_flag (G : ctx) (i : var) : option bool := ... -``` - -These decompose `option (ty * bool)` into simple `option ty` and `option bool`, -which Rocq can discriminate/inject without issue. - -Key lemma: - -```coq -Lemma ctx_lookup_cons_zero_flag_contra : - forall (T1 : ty) (G'' : ctx) (T0 : ty), - ctx_lookup ((T1, true) :: G'') 0 = Some (T0, false) -> False. -``` - -Uses `f_equal` with functional extraction to get `Some true = Some false`, -then `discriminate` on `option bool` (which works fine). - -## Impact - -- `flags_only_increase`: **Qed** (was primary blocker) -- `ctx_mark_used_flag_at`, `ctx_mark_used_flag_other`, `ctx_mark_used_ty_other`: all Qed -- T_Let/T_LetLin/T_Case idx=0 cases in `flags_only_increase`: closed via `ctx_lookup_cons_zero_flag_contra` - -## Remaining - -Same projected approach needed for: -- `ctx_mark_used_types_agree` (1 trivial case) -- `ctx_mark_used_false_preserved` (empty ctx case) -- `typing_ctx_transfer` T_Let/T_LetLin/T_Lam/T_Case (need consumption tracking conjunct) - -## Import Order Fix - -```coq -(* Typing.v and Semantics.v: import String BEFORE List *) -Require Import Coq.Strings.String. (* Before List so List.length wins *) -Require Import Coq.Lists.List. -``` diff --git a/formal/DESIGN-NOTE-2026-03-28.adoc b/formal/DESIGN-NOTE-2026-03-28.adoc new file mode 100644 index 00000000..4240e2f9 --- /dev/null +++ b/formal/DESIGN-NOTE-2026-03-28.adoc @@ -0,0 +1,87 @@ +== Design Note: Environment-Leaking Bug and Substitution-Based Fix + +*Date*: 2026-03-28 *Author*: Jonathan D.A. Jewell (hyperpolymath) +*Status*: Implemented + +=== Problem: Environment Leaking in Congruence Rules + +The original operational semantics used a runtime environment +(`+env = list runtime_val+`) in the configuration +`+(mem * region_env * env * expr)+`. Binding reductions (S_Let_Val, +S_App_Fun, S_Case_Inl/Inr) extended the environment, and congruence +rules (S_Let_Step, S_App_Step1/2, etc.) propagated the post-step +environment to sibling expressions. + +This caused an *environment-leaking bug* where nested binding reductions +leaked environment extensions to sibling sub-expressions, making +*preservation genuinely false*. + +==== Concrete Counterexample + +.... +let x = (let y = 42 in body) in e2 +.... + +[arabic] +. Inner step (S_Let_Val): +`+(mu, R, rho, let y = 42 in body) -> (mu, R, 42::rho, body)+` +. Outer congruence (S_Let_Step): +`+(mu, R, rho, let x = (let y = 42 in body) in e2) -> (mu, R, 42::rho, let x = body in e2)+` +. Now `+e2+` runs in `+42::rho+` — its De Bruijn index 1 points to +`+42+` instead of `+rho[0]+` +. `+let x = body in e2+` is *genuinely ill-typed*: `+body+`’s context +has the extra binding that shifts `+e2+`’s indices, and no typing +derivation exists in any context. + +=== Fix: Substitution-Based Semantics + +The configuration was simplified to `+(mem * region_env * expr)+` — no +runtime environment. + +Binding reductions now use De Bruijn substitution: - `+S_Let_Val+`: +`+let v in e2 -> subst 0 v e2+` - `+S_App_Fun+`: +`+(fn(T) -> body) v -> subst 0 v body+` - `+S_Case_Inl+`: +`+case (inl v) e1 e2 -> subst 0 v e1+` - `+S_Case_Inr+`: +`+case (inr v) e1 e2 -> subst 0 v e2+` + +The `+S_Var+` rule was removed — variables are resolved by substitution +at binding sites. + +Congruence rules no longer propagate environment changes (there is no +environment). + +=== Impact on Proofs + +[width="100%",cols="38%,33%,29%",options="header",] +|=== +|Theorem |Before |After +|values_dont_step |Qed |Qed (updated for new config) + +|no_leaks |Qed |Qed (updated for new config) + +|flags_only_increase |— |Qed (new) + +|typing_preserves_bindings |Qed |Qed (unchanged) + +|canonical forms (6) |Qed |Qed (unchanged) + +|memory_safety |Qed |Removed (env-dependent) + +|progress |Qed |Needs rewrite (no env_consistent) + +|preservation |Admitted (env-leak) |Admitted (needs subst lemma + ctx +transfer) +|=== + +The preservation proof now has a clear path to Qed via: 1. *Substitution +lemma*: handles reduction cases 2. *Context transfer lemma*: handles +congruence cases 3. *flags_only_increase*: typing only changes +used-flags false→true + +=== Key Insight + +The previous preservation failure was not a proof technique limitation — +it was a *soundness bug in the semantics*. The substitution-based +semantics is the standard formulation for De Bruijn metatheory and makes +preservation provable via well-known techniques (substitution lemma + +context weakening). diff --git a/formal/DESIGN-NOTE-2026-03-28.md b/formal/DESIGN-NOTE-2026-03-28.md deleted file mode 100644 index d767a673..00000000 --- a/formal/DESIGN-NOTE-2026-03-28.md +++ /dev/null @@ -1,66 +0,0 @@ -# Design Note: Environment-Leaking Bug and Substitution-Based Fix - -**Date**: 2026-03-28 -**Author**: Jonathan D.A. Jewell (hyperpolymath) -**Status**: Implemented - -## Problem: Environment Leaking in Congruence Rules - -The original operational semantics used a runtime environment (`env = list runtime_val`) -in the configuration `(mem * region_env * env * expr)`. Binding reductions (S_Let_Val, -S_App_Fun, S_Case_Inl/Inr) extended the environment, and congruence rules (S_Let_Step, -S_App_Step1/2, etc.) propagated the post-step environment to sibling expressions. - -This caused an **environment-leaking bug** where nested binding reductions leaked -environment extensions to sibling sub-expressions, making **preservation genuinely false**. - -### Concrete Counterexample - -``` -let x = (let y = 42 in body) in e2 -``` - -1. Inner step (S_Let_Val): `(mu, R, rho, let y = 42 in body) -> (mu, R, 42::rho, body)` -2. Outer congruence (S_Let_Step): `(mu, R, rho, let x = (let y = 42 in body) in e2) -> (mu, R, 42::rho, let x = body in e2)` -3. Now `e2` runs in `42::rho` — its De Bruijn index 1 points to `42` instead of `rho[0]` -4. `let x = body in e2` is **genuinely ill-typed**: `body`'s context has the extra binding - that shifts `e2`'s indices, and no typing derivation exists in any context. - -## Fix: Substitution-Based Semantics - -The configuration was simplified to `(mem * region_env * expr)` — no runtime environment. - -Binding reductions now use De Bruijn substitution: -- `S_Let_Val`: `let v in e2 -> subst 0 v e2` -- `S_App_Fun`: `(fn(T) -> body) v -> subst 0 v body` -- `S_Case_Inl`: `case (inl v) e1 e2 -> subst 0 v e1` -- `S_Case_Inr`: `case (inr v) e1 e2 -> subst 0 v e2` - -The `S_Var` rule was removed — variables are resolved by substitution at binding sites. - -Congruence rules no longer propagate environment changes (there is no environment). - -## Impact on Proofs - -| Theorem | Before | After | -|---------|--------|-------| -| values_dont_step | Qed | Qed (updated for new config) | -| no_leaks | Qed | Qed (updated for new config) | -| flags_only_increase | — | Qed (new) | -| typing_preserves_bindings | Qed | Qed (unchanged) | -| canonical forms (6) | Qed | Qed (unchanged) | -| memory_safety | Qed | Removed (env-dependent) | -| progress | Qed | Needs rewrite (no env_consistent) | -| preservation | Admitted (env-leak) | Admitted (needs subst lemma + ctx transfer) | - -The preservation proof now has a clear path to Qed via: -1. **Substitution lemma**: handles reduction cases -2. **Context transfer lemma**: handles congruence cases -3. **flags_only_increase**: typing only changes used-flags false→true - -## Key Insight - -The previous preservation failure was not a proof technique limitation — it was a -**soundness bug in the semantics**. The substitution-based semantics is the standard -formulation for De Bruijn metatheory and makes preservation provable via well-known -techniques (substitution lemma + context weakening). diff --git a/formal/ESCALATION-4.8-leaf-rule-region-liveness.adoc b/formal/ESCALATION-4.8-leaf-rule-region-liveness.adoc new file mode 100644 index 00000000..f7f6ee33 --- /dev/null +++ b/formal/ESCALATION-4.8-leaf-rule-region-liveness.adoc @@ -0,0 +1,178 @@ +== Escalation: the §4.8 leaf-rule region-liveness decision (owner call) + +*Status:* OPEN — needs an owner design decision before any further L1 +preservation proof work. Raised 2026-06-16. + +____ +=== ⚠️ UPDATE 2026-06-16 — path (3) *proven insufficient*; the real fork is effect typing + +An implementation attempt of path (3) below (strengthen `+T_Var_*_L1+` +with `+In r (free_regions T) -> In r R+`) was carried to a coqc-verified +intermediate and *disproved*. Path (3) closes the variable leaf and +every _type-preserving_ constructor, but +`+region_shrink_preserves_typing_l1_gen_m+` becomes a *false statement* +at _type-erasing eliminators_. + +*Counterexample (coqc-checked shape).* `+EDrop (EVar j)+` with +`+j : TString rr+`, at `+R = [rr]+`: - types via `+T_Drop_L1+` (TString +is linear) + strengthened `+T_Var+` (which forces `+In rr R+`, satisfied +by `+In rr [rr]+`); output type is `+TUnit+`; - +`+expr_strictly_free_of_region rr (EDrop (EVar j)) = True+` (no +syntactic `+rr+`); - `+~ In rr (free_regions TUnit)+` holds trivially +(the Tofte-Talpin premise path (3)/region_shrink can supply); - *yet* +shrinking `+rr+` to `+R = []+` leaves the inner `+EVar j : TString rr+` +untypable (`+In rr [] = False+`). So `+region_shrink+` is _false_ here. + +The same shape hides inside a *lambda value* whose body uses-and-drops a +captured `+TString rr+` variable, so an `+is_value+` restriction does +not rescue it. *Root cause:* a closure / eliminator can depend on a +region _without exposing it in its result type_; the +`+~ In r (free_regions T)+` premise constrains only the _result_ type, +which these rules erase. This same gap blocks all four targets +(region_liveness’s ~13 `+subst_typing_gen_l1_m+` call sites thread the +_substituted location’s_ region, which need not appear in the +sub-expression’s output type; step_pop’s 9 admits are the same "`inner +step pops a region free in the sibling/outer type`"). + +*Therefore the decision is now sharper than (A)/(B)/(C) below:* - *(A′) +Adopt effect typing* — deprecate the bare-`+TFun+` lambda rules +(`+T_Lam_L1_Linear+`/`+_Affine+`) and keep only the effect-typed +`+T_Lam_L1_*_Eff+` (`+TFunEff … R_in R_out+`), whose `+free_regions+` +already includes `+R_in ++ R_out+`. Then every region dependency is +type-visible and the threading composes through eliminators. This is +path (1), already half-built (the TFunEff "`live line`"). *Recommended — +but it changes the accepted surface language: it rejects +region-capturing bare-`+TFun+` closures. That rejection is the owner’s +call.* - *(B′) A context-region-freedom precondition* on +`+region_shrink+` ("`no variable position `+e+` reads carries `+rr+` in +its type`"), a new predicate over `+e × G+` threaded from call sites. +Heavier; no surface change. - *(C′) Leave the four admits* — but note +the path-(3) `+T_Var+` premise *cannot coexist with a green build* +without (A′)/(B′), so status-quo means _also reverting_ the `+T_Var+` +strengthening (done — `+formal/+` is back to clean +green-with-one-axiom). + +No code from the attempt remains; `+formal/+` is clean +green-with-admits. The recommendation below (plain "`(A)`") is +*superseded* by (A′) above. +____ + +*Why this is an escalation and not a patch.* `+CLAUDE.md+` → +_Anti-pattern detector_ lists "`strengthened lemma signatures within the +current judgment shape`" and "`sibling-region-disjointness side +conditions`" as *stop-and-escalate* triggers, and _DO #4_ says +"`escalate before patching.`" The fix below is exactly a leaf-rule +strengthening of the *surface typing judgment*, so it changes what +programs ephapax accepts. That is a language-design choice, not a proof +trick — it is yours to make. + +=== The finding (ground-truthed with `+coqc 8.18.0+`, `+Print Assumptions+`) + +The L1 development on `+main+` is green with *one* surfacing axiom +(`+region_liveness_at_split_l1_gen+`). The real open admits are four, +all in `+formal/Semantics_L1.v+`, and *all four collapse to one +decision*: + +[width="100%",cols="25%,25%,25%,25%",options="header",] +|=== +|Lemma |Line |Internal admits |Nature +|`+region_shrink_preserves_typing_l1_gen_m+` |:441 (Admitted :678) |2 +(:576, :646) |shadowed `+rr = r+` case of `+T_Region_Active_L1{,_Echo}+` + +|`+region_liveness_at_split_l1_gen+` |:1942 (Admitted :2028) |2 (:1994, +:2014) |*FALSE as stated* (see below) + +|`+step_pop_disjoint_from_type_l1+` |:3178 (Admitted :3433) |~9 |inner +step pops a region free in the sibling/outer type + +|`+preservation_l1+` |:3439 (Admitted :3452) |1 outer (whole body +deferred) |capstone, derivative of the three above +|=== + +Inventory corrections (verified): `+subst_preserves_typing_l1+` (:3067) +is `+Qed+`, *not* admitted. `+TypingL2.v+` has *zero* admits/axioms. +`+preservation_l3+` (:3568) is `+Qed+`, conditional only on +`+region_shrink_preserves_typing_l1_gen_m+`. So the opportunistic +honestly-closable wins are *already taken* — there is nothing for me to +close without the decision below. + +==== `+region_liveness_at_split_l1_gen+` is false as written + +Statement: `+In rv R -> In rv R'+` after +`+R ; G |=L1[m] e : T -| R' ; G'+`. Counterexample in the source: +`+ERegion rv (EI32 5)+` at `+R = [rv]+` — the `+T_Region_Active_L1+` +rule pops the only `+rv+`, so `+rv ∈ R+` but `+rv ∉ R'+`. No tactic +closes a false statement; the _judgment_ must change. + +=== The single decision + +*PRESERVATION-DESIGN.md §4.8, path (3): strengthen the leaf rules* +`+T_Var_Lin_L1+`, `+T_Var_Unr_L1+`, `+T_Loc_L1+` (and the `+TString r+` +consumers `+T_StringConcat_L1+` / `+T_Drop_L1+`) with the formation +premise: + +.... + In r (free_regions T) -> In r R +.... + +i.e. "`a value whose type mentions region `+r+` can only be used where +`+r+` is live.`" This makes _region-liveness of a typed value_ a +*derivable invariant of the judgment* rather than a side condition +stated per compound rule. Consequences, in order: + +[arabic] +. `+region_liveness_at_split_l1_gen+` becomes provable (the +`+T_Region_Active+` `+r = rv+` case is now vacuous: a body whose _type_ +references `+rv+` cannot have popped `+rv+`). +. `+step_pop_disjoint_from_type_l1+`’s ~9 hard cases discharge by +structural induction (the missing inner-typing premise is now present at +the variable/loc leaves). +. `+region_shrink_preserves_typing_l1_gen_m+`’s shadowed case closes via +the commutation already proved for the descend case. +. `+preservation_l1+` is re-derived as the capstone (shape drafted in +PRESERVATION-DESIGN.md §4.5). + +Path (1) — effect-typed `+TFunEff+` lambdas — is *already in the +codebase* and additionally closes the `+S_App_Step2+` case; (3) is the +complementary leaf-rule half. They are one coherent layer-design change, +not three. + +=== What the owner is actually choosing + +This is *not* free. The premise `+In r (free_regions T) -> In r R+` is a +real restriction on the _surface language_: it rejects programs that +hold a value typed against a region that is no longer live. That is +almost certainly the intended discipline (it is the linear/region story +ephapax is built on), but it is a *semantics decision with UX +consequences*, so it needs your sign-off rather than my unilateral edit. +Options: + +* *(A) Adopt §4.8 path (3) leaf-rule strengthening* (recommended; +unifies all four admits; matches the four-layer intent). Cost: the +surface judgment gets stricter; a handful of `+TypingL1.v+` rules grow a +premise and every leaf-rule inversion in `+Semantics_L1.v+` must thread +it. +* *(B) Multiset reformulation of `+remove_first_L1+`* (the in-file +option (b)). Heavier: loses list ordering, ripples through every +`+remove_first_eq_l1+` user. Dispreferred fallback. +* *(C) Leave the four admits as documented L2-β follow-ups.* The build +stays green-with-one-axiom; L3 stays conditional. No soundness +regression, but the L1 capstone stays open. + +=== Off-limits (recorded so no one re-treads it) + +`+formal/Semantics.v+` `+Theorem preservation+` (:8556, `+Admitted+` +:9258) is the *provably-false legacy theorem*; `+Counterexample.v+` +depends on its falsity. Per `+CLAUDE.md+` _DO NOT 1/2_ and +`+feedback_ephapax_no_patching_legacy_preservation.md+`: never close, +never add helper lemmas toward it. This escalation does *not* touch it. + +=== Recommendation + +Take *(A)*. But because it changes the accepted surface language, I am +not implementing it without your explicit go-ahead. On approval, the +work is: add the premise to the ~5 leaf rules in `+TypingL1.v+`, +re-thread the leaf-rule inversions, then close the four admits in +dependency order (region_liveness → step_pop → region_shrink → +preservation_l1), keeping `+Counterexample.v+` and the legacy theorem +untouched. diff --git a/formal/ESCALATION-4.8-leaf-rule-region-liveness.md b/formal/ESCALATION-4.8-leaf-rule-region-liveness.md deleted file mode 100644 index f7839543..00000000 --- a/formal/ESCALATION-4.8-leaf-rule-region-liveness.md +++ /dev/null @@ -1,157 +0,0 @@ - - - -# Escalation: the §4.8 leaf-rule region-liveness decision (owner call) - -**Status:** OPEN — needs an owner design decision before any further L1 -preservation proof work. Raised 2026-06-16. - -> ## ⚠️ UPDATE 2026-06-16 — path (3) **proven insufficient**; the real fork is effect typing -> -> An implementation attempt of path (3) below (strengthen `T_Var_*_L1` with -> `In r (free_regions T) -> In r R`) was carried to a coqc-verified -> intermediate and **disproved**. Path (3) closes the variable leaf and every -> *type-preserving* constructor, but `region_shrink_preserves_typing_l1_gen_m` -> becomes a **false statement** at *type-erasing eliminators*. -> -> **Counterexample (coqc-checked shape).** `EDrop (EVar j)` with -> `j : TString rr`, at `R = [rr]`: -> - types via `T_Drop_L1` (TString is linear) + strengthened `T_Var` (which -> forces `In rr R`, satisfied by `In rr [rr]`); output type is `TUnit`; -> - `expr_strictly_free_of_region rr (EDrop (EVar j)) = True` (no syntactic `rr`); -> - `~ In rr (free_regions TUnit)` holds trivially (the Tofte-Talpin premise -> path (3)/region_shrink can supply); -> - **yet** shrinking `rr` to `R = []` leaves the inner `EVar j : TString rr` -> untypable (`In rr [] = False`). So `region_shrink` is *false* here. -> -> The same shape hides inside a **lambda value** whose body uses-and-drops a -> captured `TString rr` variable, so an `is_value` restriction does not rescue -> it. **Root cause:** a closure / eliminator can depend on a region *without -> exposing it in its result type*; the `~ In r (free_regions T)` premise -> constrains only the *result* type, which these rules erase. This same gap -> blocks all four targets (region_liveness's ~13 `subst_typing_gen_l1_m` call -> sites thread the *substituted location's* region, which need not appear in -> the sub-expression's output type; step_pop's 9 admits are the same "inner -> step pops a region free in the sibling/outer type"). -> -> **Therefore the decision is now sharper than (A)/(B)/(C) below:** -> - **(A′) Adopt effect typing** — deprecate the bare-`TFun` lambda rules -> (`T_Lam_L1_Linear`/`_Affine`) and keep only the effect-typed -> `T_Lam_L1_*_Eff` (`TFunEff … R_in R_out`), whose `free_regions` already -> includes `R_in ++ R_out`. Then every region dependency is type-visible and -> the threading composes through eliminators. This is path (1), already -> half-built (the TFunEff "live line"). **Recommended — but it changes the -> accepted surface language: it rejects region-capturing bare-`TFun` -> closures. That rejection is the owner's call.** -> - **(B′) A context-region-freedom precondition** on `region_shrink` -> ("no variable position `e` reads carries `rr` in its type"), a new -> predicate over `e × G` threaded from call sites. Heavier; no surface change. -> - **(C′) Leave the four admits** — but note the path-(3) `T_Var` premise -> **cannot coexist with a green build** without (A′)/(B′), so status-quo means -> *also reverting* the `T_Var` strengthening (done — `formal/` is back to -> clean green-with-one-axiom). -> -> No code from the attempt remains; `formal/` is clean green-with-admits. The -> recommendation below (plain "(A)") is **superseded** by (A′) above. - -**Why this is an escalation and not a patch.** `CLAUDE.md` → -*Anti-pattern detector* lists "strengthened lemma signatures within the -current judgment shape" and "sibling-region-disjointness side conditions" -as **stop-and-escalate** triggers, and *DO #4* says "escalate before -patching." The fix below is exactly a leaf-rule strengthening of the -**surface typing judgment**, so it changes what programs ephapax accepts. -That is a language-design choice, not a proof trick — it is yours to make. - -## The finding (ground-truthed with `coqc 8.18.0`, `Print Assumptions`) - -The L1 development on `main` is green with **one** surfacing axiom -(`region_liveness_at_split_l1_gen`). The real open admits are four, all in -`formal/Semantics_L1.v`, and **all four collapse to one decision**: - -| Lemma | Line | Internal admits | Nature | -|---|---|---|---| -| `region_shrink_preserves_typing_l1_gen_m` | :441 (Admitted :678) | 2 (:576, :646) | shadowed `rr = r` case of `T_Region_Active_L1{,_Echo}` | -| `region_liveness_at_split_l1_gen` | :1942 (Admitted :2028) | 2 (:1994, :2014) | **FALSE as stated** (see below) | -| `step_pop_disjoint_from_type_l1` | :3178 (Admitted :3433) | ~9 | inner step pops a region free in the sibling/outer type | -| `preservation_l1` | :3439 (Admitted :3452) | 1 outer (whole body deferred) | capstone, derivative of the three above | - -Inventory corrections (verified): `subst_preserves_typing_l1` (:3067) is -`Qed`, **not** admitted. `TypingL2.v` has **zero** admits/axioms. -`preservation_l3` (:3568) is `Qed`, conditional only on -`region_shrink_preserves_typing_l1_gen_m`. So the opportunistic -honestly-closable wins are **already taken** — there is nothing for me to -close without the decision below. - -### `region_liveness_at_split_l1_gen` is false as written - -Statement: `In rv R -> In rv R'` after `R ; G |=L1[m] e : T -| R' ; G'`. -Counterexample in the source: `ERegion rv (EI32 5)` at `R = [rv]` — the -`T_Region_Active_L1` rule pops the only `rv`, so `rv ∈ R` but `rv ∉ R'`. -No tactic closes a false statement; the *judgment* must change. - -## The single decision - -**PRESERVATION-DESIGN.md §4.8, path (3): strengthen the leaf rules** -`T_Var_Lin_L1`, `T_Var_Unr_L1`, `T_Loc_L1` (and the `TString r` consumers -`T_StringConcat_L1` / `T_Drop_L1`) with the formation premise: - -``` - In r (free_regions T) -> In r R -``` - -i.e. "a value whose type mentions region `r` can only be used where `r` is -live." This makes *region-liveness of a typed value* a **derivable -invariant of the judgment** rather than a side condition stated per -compound rule. Consequences, in order: - -1. `region_liveness_at_split_l1_gen` becomes provable (the `T_Region_Active` - `r = rv` case is now vacuous: a body whose *type* references `rv` cannot - have popped `rv`). -2. `step_pop_disjoint_from_type_l1`'s ~9 hard cases discharge by structural - induction (the missing inner-typing premise is now present at the - variable/loc leaves). -3. `region_shrink_preserves_typing_l1_gen_m`'s shadowed case closes via the - commutation already proved for the descend case. -4. `preservation_l1` is re-derived as the capstone (shape drafted in - PRESERVATION-DESIGN.md §4.5). - -Path (1) — effect-typed `TFunEff` lambdas — is **already in the codebase** -and additionally closes the `S_App_Step2` case; (3) is the complementary -leaf-rule half. They are one coherent layer-design change, not three. - -## What the owner is actually choosing - -This is **not** free. The premise `In r (free_regions T) -> In r R` is a -real restriction on the *surface language*: it rejects programs that hold a -value typed against a region that is no longer live. That is almost -certainly the intended discipline (it is the linear/region story ephapax is -built on), but it is a **semantics decision with UX consequences**, so it -needs your sign-off rather than my unilateral edit. Options: - -- **(A) Adopt §4.8 path (3) leaf-rule strengthening** (recommended; unifies - all four admits; matches the four-layer intent). Cost: the surface - judgment gets stricter; a handful of `TypingL1.v` rules grow a premise and - every leaf-rule inversion in `Semantics_L1.v` must thread it. -- **(B) Multiset reformulation of `remove_first_L1`** (the in-file option - (b)). Heavier: loses list ordering, ripples through every - `remove_first_eq_l1` user. Dispreferred fallback. -- **(C) Leave the four admits as documented L2-β follow-ups.** The build - stays green-with-one-axiom; L3 stays conditional. No soundness regression, - but the L1 capstone stays open. - -## Off-limits (recorded so no one re-treads it) - -`formal/Semantics.v` `Theorem preservation` (:8556, `Admitted` :9258) is the -**provably-false legacy theorem**; `Counterexample.v` depends on its -falsity. Per `CLAUDE.md` *DO NOT 1/2* and -`feedback_ephapax_no_patching_legacy_preservation.md`: never close, never -add helper lemmas toward it. This escalation does **not** touch it. - -## Recommendation - -Take **(A)**. But because it changes the accepted surface language, I am -not implementing it without your explicit go-ahead. On approval, the work -is: add the premise to the ~5 leaf rules in `TypingL1.v`, re-thread the -leaf-rule inversions, then close the four admits in dependency order -(region_liveness → step_pop → region_shrink → preservation_l1), keeping -`Counterexample.v` and the legacy theorem untouched. diff --git a/formal/INTERACTIVE-PROOF-SESSION.adoc b/formal/INTERACTIVE-PROOF-SESSION.adoc new file mode 100644 index 00000000..dcd07226 --- /dev/null +++ b/formal/INTERACTIVE-PROOF-SESSION.adoc @@ -0,0 +1,89 @@ +== Interactive Proof Session: Close T_Case/T_If + +=== Setup + +[source,bash] +---- +cd /var/mnt/eclipse/repos/nextgen-languages/ephapax/formal +# Open in CoqIDE or vscoq: +coqide -R . Ephapax Semantics.v +# Or: code Semantics.v (with vscoq extension, add -R . Ephapax to settings) +---- + +=== What to do + +==== Step 1: Navigate to line ~932 + +Find the `+all: admit.+` inside `+no_consumption_at_true_linear+`. Step +through the proof until you reach the T_Case bullet. + +==== Step 2: At T_Case goal, run these tactics one at a time: + +[source,coq] +---- +(* After stepping past all the closed cases, you should be at T_Case *) +- inversion H2; subst. + (* NOW: Look at the proof context. You should see: + - IHe1 : ... (IH for scrutinee e) + - IHe2 : ... (IH for branch e1) + - IHe3 : ... (IH for branch e2) + - Some typing hypothesis for the second derivation's branches + + Write down the EXACT names you see. Then: *) + + (* Apply IH for branch e1 at shifted index S i *) + eapply IHe2 with (i0 := S i). (* or whatever the IH parameter is named *) + (* Fill each subgoal — the pattern is: + - is_linear_ty T0 = true → assumption + - ctx_lookup (ctx_extend G' T1) (S i) = Some (T0, true) → simpl; eapply flags_monotone; eassumption + - ctx_lookup ((T1, true) :: G_final) (S i) = Some (T0, true) → simpl; assumption + - ctx_types_agree (ctx_extend G' T1) (ctx_extend G2' T1) → apply ctx_extend_types_agree; eapply typing_preserves_types_agree; eassumption + - ctx_lookup (ctx_extend G2' T1) (S i) = Some (T0, false) → simpl; eapply IHe1; eassumption + - The typing derivation for the second branch → eassumption + *) +---- + +==== Step 3: Same for T_If (simpler — no binder shift) + +[source,coq] +---- +- inversion H2; subst. + eapply IHe2; try eassumption. + + eapply typing_preserves_types_agree; eassumption. + + eapply flags_monotone; eassumption. + + eapply IHe1; eassumption. +---- + +==== Step 4: Check remaining admits + +After T_Case and T_If are closed, the `+all: admit.+` should have fewer +goals. If any remain, they’re binding cases (T_Let, T_LetLin) that +follow the same S i pattern as T_Case. + +==== Step 5: Propagate to other theorems + +Once `+no_consumption_at_true_linear+` is Qed: - `+typing_ctx_transfer+` +uses it — may close more cases - `+subst_preserves_typing+` has +independent binding issues - `+preservation+` depends on both + +=== Key lemmas available + +* `+typing_preserves_types_agree+` : threading types_agree through a +derivation +* `+flags_monotone+` : flags only increase (false→true, never +true→false) +* `+ctx_extend_types_agree+` : extending preserves types_agree +* `+ctx_extend_false_preserved+` : extending preserves false_preserved +* `+ctx_lookup_extend_succ+` : ctx_lookup (ctx_extend G T) (S i) = +ctx_lookup G i +* `+consumption_chain+` : consumption composes through two derivations + +=== The critical insight + +The IH is quantified over `+i+` (we generalized it). For binding cases: +- Original context has `+(T0, true)+` at position `+i+` - Extended +context `+(T1, false) :: G+` has `+(T0, true)+` at position `+S i+` - +The IH at `+S i+` gives the result at `+S i+` in the output - +`+ctx_lookup ((T1, ??) :: G_final) (S i) = ctx_lookup G_final i+` + +This is why the generalization was necessary. diff --git a/formal/INTERACTIVE-PROOF-SESSION.md b/formal/INTERACTIVE-PROOF-SESSION.md deleted file mode 100644 index a345ccab..00000000 --- a/formal/INTERACTIVE-PROOF-SESSION.md +++ /dev/null @@ -1,84 +0,0 @@ -# Interactive Proof Session: Close T_Case/T_If - -## Setup - -```bash -cd /var/mnt/eclipse/repos/nextgen-languages/ephapax/formal -# Open in CoqIDE or vscoq: -coqide -R . Ephapax Semantics.v -# Or: code Semantics.v (with vscoq extension, add -R . Ephapax to settings) -``` - -## What to do - -### Step 1: Navigate to line ~932 - -Find the `all: admit.` inside `no_consumption_at_true_linear`. -Step through the proof until you reach the T_Case bullet. - -### Step 2: At T_Case goal, run these tactics one at a time: - -```coq -(* After stepping past all the closed cases, you should be at T_Case *) -- inversion H2; subst. - (* NOW: Look at the proof context. You should see: - - IHe1 : ... (IH for scrutinee e) - - IHe2 : ... (IH for branch e1) - - IHe3 : ... (IH for branch e2) - - Some typing hypothesis for the second derivation's branches - - Write down the EXACT names you see. Then: *) - - (* Apply IH for branch e1 at shifted index S i *) - eapply IHe2 with (i0 := S i). (* or whatever the IH parameter is named *) - (* Fill each subgoal — the pattern is: - - is_linear_ty T0 = true → assumption - - ctx_lookup (ctx_extend G' T1) (S i) = Some (T0, true) → simpl; eapply flags_monotone; eassumption - - ctx_lookup ((T1, true) :: G_final) (S i) = Some (T0, true) → simpl; assumption - - ctx_types_agree (ctx_extend G' T1) (ctx_extend G2' T1) → apply ctx_extend_types_agree; eapply typing_preserves_types_agree; eassumption - - ctx_lookup (ctx_extend G2' T1) (S i) = Some (T0, false) → simpl; eapply IHe1; eassumption - - The typing derivation for the second branch → eassumption - *) -``` - -### Step 3: Same for T_If (simpler — no binder shift) - -```coq -- inversion H2; subst. - eapply IHe2; try eassumption. - + eapply typing_preserves_types_agree; eassumption. - + eapply flags_monotone; eassumption. - + eapply IHe1; eassumption. -``` - -### Step 4: Check remaining admits - -After T_Case and T_If are closed, the `all: admit.` should have fewer goals. -If any remain, they're binding cases (T_Let, T_LetLin) that follow the same -S i pattern as T_Case. - -### Step 5: Propagate to other theorems - -Once `no_consumption_at_true_linear` is Qed: -- `typing_ctx_transfer` uses it — may close more cases -- `subst_preserves_typing` has independent binding issues -- `preservation` depends on both - -## Key lemmas available - -- `typing_preserves_types_agree` : threading types_agree through a derivation -- `flags_monotone` : flags only increase (false→true, never true→false) -- `ctx_extend_types_agree` : extending preserves types_agree -- `ctx_extend_false_preserved` : extending preserves false_preserved -- `ctx_lookup_extend_succ` : ctx_lookup (ctx_extend G T) (S i) = ctx_lookup G i -- `consumption_chain` : consumption composes through two derivations - -## The critical insight - -The IH is quantified over `i` (we generalized it). For binding cases: -- Original context has `(T0, true)` at position `i` -- Extended context `(T1, false) :: G` has `(T0, true)` at position `S i` -- The IH at `S i` gives the result at `S i` in the output -- `ctx_lookup ((T1, ??) :: G_final) (S i) = ctx_lookup G_final i` - -This is why the generalization was necessary. diff --git a/formal/L1-CHOREO-EXPERIMENT.adoc b/formal/L1-CHOREO-EXPERIMENT.adoc new file mode 100644 index 00000000..66b639d1 --- /dev/null +++ b/formal/L1-CHOREO-EXPERIMENT.adoc @@ -0,0 +1,128 @@ +== Deciding experiment — does subject reduction carry region-liveness through a region exit? + +*Status: RUN. Verdict: RELOCATES (not "`carries coherently`").* +2026-06-16. + +This is the cheap, decisive experiment that +link:./L1-ELIMINATOR-FORK.md[`+L1-ELIMINATOR-FORK.md+`] §5/§6 flagged as +the single thing to run before committing to the +choreographic-foundational route. Machine-checked skeleton: +link:./L1ChoreoExperiment.v[`+L1ChoreoExperiment.v+`] (5 Qed lemmas, +axiom-free, in the build; closes no admit, adds no axiom). + +=== The question + +Model one region `+rv+`’s lifecycle as a two-segment choreography + +.... +Gx ::= open[rv] . use[rv](j) . close[rv] . end +.... + +`+seg1 = [open .. use]+` (rv live), `+seg2 = [close .. end]+` (rv dead). +Does subject reduction at the `+close[rv]+` boundary *definitionally +discharge* "`rv live throughout seg1`", or does coherence of the +post-close projection become a *fresh hypothesis*? + +=== Method + +3-phase adversarial workflow (`+wf_300a9737+`): parallel reconnaissance +of the exact Coq region-semantics shapes + the Lean tropical +session-type ingredients + the eliminator-fork framing → a design +synthesising the minimal model and a verdict → a 3-lens adversarial +verify (soundness, Coq-buildability/honesty, consistency with the locked +ground truth). The design’s verdict survived consistency; soundness and +buildability *did not endorse it as framed* — and in failing they +_sharpened_ it into a stronger negative result, recorded below and +mechanised in the `+.v+`. + +=== Verdict: relocates — and two corrections that make it sharper + +*The obligation does not vanish under the choreographic reframe; it +relocates into a projection-coherence side condition that is, +definitionally, exactly `+expr_no_exit_of_region+` — and at the genuine +boundary that condition is provably FALSE.* Two corrections to the naive +framing, both now Qed: + +[arabic] +. *`+EDrop (EVar j)+` is the wrong probe.* It reduces by `+S_Drop+`, +which leaves the region environment `+R+` unchanged, and *never fires +`+S_Region_Exit+`*. It crosses no boundary; it is carried +unconditionally (`+edrop_evar_carries_liveness+`, via the existing Qed +`+region_liveness_no_exit_l1_gen+`, because +`+expr_no_exit_of_region rv (EDrop (EVar j)) = True+`). The +`+close[rv]+` boundary exists *only* when an `+ERegion rv (…)+` frame +wraps the term — so the choreographic `+close+` action projects onto the +`+ERegion+` frame, not onto `+EDrop+`. +. *At the genuine boundary the relocated condition is unsatisfiable.* +`+projection_coherent_after_close rv e := expr_no_exit_of_region rv e+` +(`+coherence_is_no_exit+`, reflexivity), and on the only +boundary-crossing term `+expr_no_exit_of_region rv (ERegion rv e)+` +reduces to `+rv <> rv ∧ …+` = *False* (`+region_frame_breaks_no_exit+`). +So the relocation does not _close_ the obligation; it surfaces it where +it is false unless the language forbids inner re-entry. Mechanised +precisely as `+close_derives_coherence_needs_no_inner_exit+`: the +apparatus-level proposition `+close_derives_coherence+` would force that +*no* program with an inner same-region re-frame +`+ERegion rv (ERegion rv e0)+` ever types with `+rv+` live. + +`+close_derives_coherence+` itself is left as a bare `+Definition+` (a +`+Prop+`), *never proved* — the honest record of the open obligation, +not a faked `+Qed+` and not an `+Admitted+`. + +=== Where the real fork actually lives (verifier soundness lens) + +*Not* in the clean `+ERegion rv+` region-exit case — that case is +already *Qed* (`+Semantics_L1.v:3462-3493+`), protected by the +Tofte–Talpin premise `+~ In r (free_regions T)+` in +`+T_Region_Active_L1+`. The genuinely admitted hard cases are the +*congruence rules* `+S_Let_Step+` / `+S_App_Step2+` / `+S_Pair_Step2+` / +`+S_Case_Step+` (`+Semantics_L1.v:3271-3284+`), where an *inner step +pops a region erased from the outer result type*. The 2-segment +`+EDrop+`/`+ERegion+` experiment does not reach them — so the +_corrected_ next experiment must target the congruence cases, not the +clean exit. + +=== Implications for the four open L1 admits + +The choreographic reframe *converts* the four admits (four faces of one +invariant — see the call-site audit at `+Semantics_L1.v:2032-2052+`) +into a single global-type coherence obligation = +`+expr_no_exit_of_region+` promoted to a foundational invariant. That is +genuine consolidation (*4 → 1*, the right shape) but *not closure* (1 ≠ +0). It confirms "`four faces of ONE invariant`"; it does not dissolve +them to `+rfl+`. + +[width="100%",cols="50%,50%",options="header",] +|=== +|Admit |Under the reframe +|#1 `+region_shrink+` |the `+close[rv]+` action; coherent iff residual +is `+expr_no_exit_of_region rv+` + +|#2 `+region_liveness+` (false as stated) |false snapshot +`+In rv R → In rv R'+` replaced by "`Gx advanced past `+use[rv]+``", +valid only under coherence + +|#3 `+step_pop+` |the congruence-case fork; best-shaped face, but +discharged *only* under projection coherence + +|#4 `+preservation_l1+` |the capstone = subject reduction for the +choreography; closes iff `+close_derives_coherence+` holds at every exit +|=== + +=== What this does NOT settle / next steps + +[arabic] +. `+close_derives_coherence+` is unproved (and false in general). A +"`carries coherently`" verdict is unavailable for the full language. +. No `+has_type_global+` / projection / `+coherent(Gx)+` / +`+close+`-reduction apparatus is built — that is the *next experiment*, +aimed at the *congruence cases* above. +. The Lean tropical side supplies the monotonicity backbone +(`+matStar_mono+`, `+id_le_star+`, `+parametric_resource_transport+`) +but has no choreography, time segments, grade vector, reduction relation +or liveness theorem; the graded unification is motivated, not +mechanised. +. *This must not gate* the clean-win deterministic carrier refactor for +admits 1/2 +(link:./L1-REGION-REFOUNDATION-PLAN.md[`+L1-REGION-REFOUNDATION-PLAN.md+`]); +that route is independent of this choreographic research on admits 3/4. diff --git a/formal/L1-CHOREO-EXPERIMENT.md b/formal/L1-CHOREO-EXPERIMENT.md deleted file mode 100644 index fe2558ba..00000000 --- a/formal/L1-CHOREO-EXPERIMENT.md +++ /dev/null @@ -1,112 +0,0 @@ - - - -# Deciding experiment — does subject reduction carry region-liveness through a region exit? - -**Status: RUN. Verdict: RELOCATES (not "carries coherently").** 2026-06-16. - -This is the cheap, decisive experiment that -[`L1-ELIMINATOR-FORK.md`](./L1-ELIMINATOR-FORK.md) §5/§6 flagged as the -single thing to run before committing to the choreographic-foundational -route. Machine-checked skeleton: -[`L1ChoreoExperiment.v`](./L1ChoreoExperiment.v) (5 Qed lemmas, axiom-free, -in the build; closes no admit, adds no axiom). - -## The question - -Model one region `rv`'s lifecycle as a two-segment choreography - -``` -Gx ::= open[rv] . use[rv](j) . close[rv] . end -``` - -`seg1 = [open .. use]` (rv live), `seg2 = [close .. end]` (rv dead). Does -subject reduction at the `close[rv]` boundary **definitionally discharge** -"rv live throughout seg1", or does coherence of the post-close projection -become a **fresh hypothesis**? - -## Method - -3-phase adversarial workflow (`wf_300a9737`): parallel reconnaissance of the -exact Coq region-semantics shapes + the Lean tropical session-type -ingredients + the eliminator-fork framing → a design synthesising the -minimal model and a verdict → a 3-lens adversarial verify (soundness, -Coq-buildability/honesty, consistency with the locked ground truth). The -design's verdict survived consistency; soundness and buildability **did not -endorse it as framed** — and in failing they *sharpened* it into a stronger -negative result, recorded below and mechanised in the `.v`. - -## Verdict: relocates — and two corrections that make it sharper - -**The obligation does not vanish under the choreographic reframe; it -relocates into a projection-coherence side condition that is, definitionally, -exactly `expr_no_exit_of_region` — and at the genuine boundary that condition -is provably FALSE.** Two corrections to the naive framing, both now Qed: - -1. **`EDrop (EVar j)` is the wrong probe.** It reduces by `S_Drop`, which - leaves the region environment `R` unchanged, and **never fires - `S_Region_Exit`**. It crosses no boundary; it is carried unconditionally - (`edrop_evar_carries_liveness`, via the existing Qed - `region_liveness_no_exit_l1_gen`, because - `expr_no_exit_of_region rv (EDrop (EVar j)) = True`). The `close[rv]` - boundary exists **only** when an `ERegion rv (…)` frame wraps the term — - so the choreographic `close` action projects onto the `ERegion` frame, - not onto `EDrop`. - -2. **At the genuine boundary the relocated condition is unsatisfiable.** - `projection_coherent_after_close rv e := expr_no_exit_of_region rv e` - (`coherence_is_no_exit`, reflexivity), and on the only boundary-crossing - term `expr_no_exit_of_region rv (ERegion rv e)` reduces to `rv <> rv ∧ …` - = **False** (`region_frame_breaks_no_exit`). So the relocation does not - *close* the obligation; it surfaces it where it is false unless the - language forbids inner re-entry. Mechanised precisely as - `close_derives_coherence_needs_no_inner_exit`: the apparatus-level - proposition `close_derives_coherence` would force that **no** program with - an inner same-region re-frame `ERegion rv (ERegion rv e0)` ever types with - `rv` live. - -`close_derives_coherence` itself is left as a bare `Definition` (a `Prop`), -**never proved** — the honest record of the open obligation, not a faked -`Qed` and not an `Admitted`. - -## Where the real fork actually lives (verifier soundness lens) - -**Not** in the clean `ERegion rv` region-exit case — that case is already -**Qed** (`Semantics_L1.v:3462-3493`), protected by the Tofte–Talpin premise -`~ In r (free_regions T)` in `T_Region_Active_L1`. The genuinely admitted -hard cases are the **congruence rules** `S_Let_Step` / `S_App_Step2` / -`S_Pair_Step2` / `S_Case_Step` (`Semantics_L1.v:3271-3284`), where an **inner -step pops a region erased from the outer result type**. The 2-segment -`EDrop`/`ERegion` experiment does not reach them — so the *corrected* next -experiment must target the congruence cases, not the clean exit. - -## Implications for the four open L1 admits - -The choreographic reframe **converts** the four admits (four faces of one -invariant — see the call-site audit at `Semantics_L1.v:2032-2052`) into a -single global-type coherence obligation = `expr_no_exit_of_region` promoted -to a foundational invariant. That is genuine consolidation (**4 → 1**, the -right shape) but **not closure** (1 ≠ 0). It confirms "four faces of ONE -invariant"; it does not dissolve them to `rfl`. - -| Admit | Under the reframe | -|---|---| -| #1 `region_shrink` | the `close[rv]` action; coherent iff residual is `expr_no_exit_of_region rv` | -| #2 `region_liveness` (false as stated) | false snapshot `In rv R → In rv R'` replaced by "Gx advanced past `use[rv]`", valid only under coherence | -| #3 `step_pop` | the congruence-case fork; best-shaped face, but discharged **only** under projection coherence | -| #4 `preservation_l1` | the capstone = subject reduction for the choreography; closes iff `close_derives_coherence` holds at every exit | - -## What this does NOT settle / next steps - -1. `close_derives_coherence` is unproved (and false in general). A "carries - coherently" verdict is unavailable for the full language. -2. No `has_type_global` / projection / `coherent(Gx)` / `close`-reduction - apparatus is built — that is the **next experiment**, aimed at the - **congruence cases** above. -3. The Lean tropical side supplies the monotonicity backbone - (`matStar_mono`, `id_le_star`, `parametric_resource_transport`) but has no - choreography, time segments, grade vector, reduction relation or liveness - theorem; the graded unification is motivated, not mechanised. -4. **This must not gate** the clean-win deterministic carrier refactor for - admits 1/2 ([`L1-REGION-REFOUNDATION-PLAN.md`](./L1-REGION-REFOUNDATION-PLAN.md)); - that route is independent of this choreographic research on admits 3/4. diff --git a/formal/L1-ELIMINATOR-FORK.adoc b/formal/L1-ELIMINATOR-FORK.adoc new file mode 100644 index 00000000..db80dd2d --- /dev/null +++ b/formal/L1-ELIMINATOR-FORK.adoc @@ -0,0 +1,279 @@ +== The eliminator fork — and whether choreographic typing across time segments closes it + +The two admits the tropical re-foundation does *not* dissolve +(`+step_pop_disjoint_from_type_l1+`, ADMIT 3; `+preservation_l1+`, ADMIT +4 gated on it). This note states the challenge precisely, explains why +both prior fixes failed, and assesses the proposal: *type +region-liveness choreographically, across time segments.* Verdict up +front: it is the right _shape_ — more so than the grade-in-context patch +— but it is unproven and can _relocate_ rather than _close_, so it needs +the minimal-counterexample experiment in §5. + +=== 1. The challenge, precisely + +Preservation needs: if `+R ; G ⊢ e : T -| R' ; G'+` and +`+(μ,R,e) → (μ',R₂,e')+`, then `+R₂ ; G ⊢ e' : T -| …+`. It fails on a +single shape: + +____ +A *subterm `+e_sub+` depends on a region `+rv+`* (its type mentions +`+rv+`, e.g. `+e_sub : TString rv+`), but an *eliminator/context erases +`+rv+` from the result type* (`+EDrop(·):TUnit+`, `+Fst+`, application, +`+Let+`-body, `+Case+`-arm), and a step *exits `+rv+`* (`+R → R₂+`, +`+rv+` gone). Now `+e'+` still contains `+e_sub+`, which needs `+rv+` +live, but `+R₂+` no longer has it and `+T+` never showed it. +____ + +Minimal Coq-checked witness: `+EDrop (EVar j : TString rv)+` at +`+R = [rv]+`. It types; `+expr_strictly_free_of_region rv+` holds (no +syntactic `+rv+`); the result type `+Unit+` satisfies the Tofte–Talpin +premise — yet shrinking `+rv+` leaves the inner `+EVar j+` untypable. + +*The root is a representational mismatch in _time_.* The judgment +expresses region liveness with: - a *snapshot* `+R+` (the live set _at +one point_), and - a *result-type-local* view (`+free_regions(T)+`), + +but the actual obligation is *temporally extended and non-local*: +"``+rv+` must be live __throughout the evaluation of `+e_sub+`__`". A +snapshot cannot say "`throughout`"; a result type cannot say "`of a +subterm whose contribution I erased`". The information needed is _when_, +and neither carrier carries time. + +=== 2. Why both prior fixes failed (the same wall) + +* *Leaf-rule strengthening (path 3):* put `+free_regions(T) ⊆ R+` on +`+T_Var+`. This forces liveness _at the variable use site_. But +region-shrink/exit _wants to remove_ `+rv+` while that very variable is +in scope, so the premise and `+region_shrink+` *contradict* — the +`+EDrop+` witness is exactly the contradiction. It moves the snapshot +demand to the leaf; it does not add time. +* *Effect-typed lambdas (A′):* makes a _lambda’s_ footprint +type-visible. Helps only `+S_App_Step2+`; the witness is a _free +variable under an eliminator_, no lambda involved. It adds +type-visibility for one binder shape; it does not add time. + +Both stay inside the snapshot+result-type ontology. The wall is that +ontology. + +=== 3. The proposal: liveness as a choreography over time segments + +Replace "`liveness = membership in a snapshot`" with "`liveness = a +position in a **global protocol over time segments**`", and recover the +per-subterm rule by *projection*. Concretely: + +* *Time segments.* A region scope `+enter rv … exit rv+` _is_ a segment: +the interval during which `+rv+` is live. Nesting/re-entry gives nested +segments (depth = the tropical `+cnt rv+` grade — the two views meet +here). +* *Regions as session-typed resources.* Each region is a participant +whose lifecycle is a session `+open → use* → close+` (exactly a +file-handle/typestate protocol). The whole program is the +*choreography*: the interleaving of all regions’ sessions across +segments. +* *Local typing = endpoint projection.* A subterm `+e_sub+` is typed by +_projecting_ the choreography onto `+e_sub+`’s segment. Its dependency +on `+rv+` is satisfied because the projection places `+e_sub+` _inside_ +`+rv+`’s `+open…close+` segment — *not* because `+rv+` appears in the +result type. +* *Preservation = subject reduction for the choreography.* A step +advances the global type to a coherent global type; the snapshot problem +dissolves because the global type carries the _temporal_ structure the +snapshot lacked. + +The eliminator’s "`hidden`" dependency is then *not hidden*: the global +type records that `+e_sub+` runs in `+rv+`’s segment, even though the +local/erased result type does not. That is precisely the missing _when_. + +=== 4. Why this is the right shape — and the estate already has both halves + +The dependency is temporal-and-global; choreographies are _the_ device +for temporal-global protocols projected to local views. This is a +structural match, not an analogy. And it composes with the tropical +re-foundation rather than competing with it: + +* `+tropical-resource-typing+` already ships +*`+TropicalSessionTypes.lean+`*. Session types are the _local +projections_ of a choreography. So region liveness becomes a +*tropical-graded choreography across time segments*: the grade (MinPlus +`+cnt+`) carries the _resource quantity_ (re-entry depth), the +session/choreographic structure carries the _temporal protocol_. One +object, two readings — the same `+echo ≡ tropical ≡ epistemic+` +unification, now extended to include the _order of events_, not just +their magnitude. +* Division of labour becomes clean: *tropical grade* closes the +structural admits (1, 2 — quantity/carrier); *choreography over +segments* is aimed at the eliminator admit (3 — time). `+EchoBridge+` +already measures L3 residues into the grade; a choreography adds the +missing temporal axis to the same grade. + +=== 5. The honest risks (why "`right shape`" ≠ "`resolved`") + +[arabic] +. *Sequential, not distributed.* Choreographies/session types were built +for _concurrent_ systems with communicating participants. The +reinterpretation here — _regions_ as participants, _scopes_ as segments, +region alloc/use/free as the "`communication`" — is principled +(resource-usage protocols / typestate are exactly session-shaped) but +*non-standard*; it needs a clean foundation, not an off-the-shelf +import. +. *Projection coherence is where the problem could re-appear.* The whole +method rests on endpoint projection being _coherent_ (locals recompose +to the global). The eliminator case might project to a local type that +is itself inconsistent — in which case the difficulty is *relocated* to +a coherence side-condition, not closed. This is the single thing to +check first. +. *Close vs relocate.* Subject reduction for the choreography must +genuinely _carry_ "``+rv+` live throughout `+e_sub+`’s segment`" through +the exit step. If it does, ADMIT 3 closes; if the segment boundary and +the exit step don’t line up definitionally, you get a new obligation, +not a discharge. +. *Apparatus cost.* This is a research program (global types, +projection, coherence, subject reduction for the region choreography), +not a refactor — high-reward, high-cost. It should not gate the +§`+L1-REGION-REFOUNDATION-PLAN+` clean win, which proceeds +independently. + +=== 6. The deciding experiment (cheap, do this before committing) + +Do *not* build the general theory first. Formulate the _minimal_ +choreography that types just `+EDrop (EVar j : TString rv)+` over two +segments — `+S₁ = [enter rv … (EVar j read) …]+`, `+S₂ = [exit rv …]+` — +and ask one question: + +____ +Does the choreography’s subject-reduction step for `+S_Region_Exit rv+` +transform the global type into a *coherent* global type in which the +(now-residual) `+EVar j+` no longer claims `+rv+`-liveness — _without_ a +snapshot premise? +____ + +* *If yes*, the segment carried the _when_, the projection stayed +coherent, and ADMIT 3 closes choreographically → re-grade +`+region_liveness+` _and_ `+step_pop+` against the global type; the +capstone follows. (This is the path to *0* admits.) +* *If no — i.e. coherence needs an extra side-condition* — the problem +has _relocated_ to that condition. Report it; it is likely the _same_ +eliminator obligation in choreographic clothing, and the honest count +stays *2* until a different idea (or a restricted language fragment) +lands. + +Either outcome is decisive and cheap, and tells us whether choreographic +typing _resolves_ the eliminator fork or merely _renames_ it. + +=== 7. Verdict + +Choreographic typing across time segments is the *best-shaped* candidate +for ADMIT 3 because it is the only proposal that adds the missing axis — +_time_ — rather than re-arranging the snapshot. It also unifies cleanly +with the tropical re-foundation via `+TropicalSessionTypes+` (a +tropically-graded choreography: quantity × order in one structure). It +is *not yet a resolution*: it can relocate the difficulty into +projection coherence, and the sequential-language foundation is +non-standard. The §5/§6 experiment is the inexpensive way to learn which +— and it is the recommended next step on the eliminator fork, run in +parallel with (not blocking) the clean-win carrier refactor. + +=== 8. Result of the §6 experiment (2026 — run in Coq) + +The §6 deciding experiment was run against the live judgment. Outcome: +the verdict is the predicted *"`no`" — it relocates* — but the +experiment also produced a concrete simplification worth keeping. + +*(a) `+step_pop_disjoint_from_type_l1+` reduces from 11 admits to 1.* +Driving the proof by `+step_R_change_shape+` (every step leaves `+R+` +fixed, prepends a fresh region, or removes exactly one) makes all ten +eliminator/erasing congruences trivial — a region free in the result +type survives unless the step exits _that exact_ region. The 255-line +induction with eleven `+admit+`s becomes ~15 lines with one. (Landed; +`+coqc 8.18.0+` + `+Print Assumptions+` verified; outer `+Admitted+` +count unchanged.) + +*(b) The single residual obligation, exactly:* the step exits `+r0+` +while `+r0 ∈ free_regions(T)+` ⟹ `+cnt r0 R ≥ 2+` +(i.e. `+In r0 (remove_first r0 R)+`). Its *direct-exit* sub-case is +_vacuous_ — a head redex `+ERegion r0 v+` is typed by +`+T_Region_Active_L1+`, whose `+~In r0 (free_regions T)+` premise +contradicts `+r0 ∈ free_regions(T)+`. The residue is the +*congruence-exit* case (`+ERegion r0+` under an eliminator; `+r0+` from +a sibling’s type — a distinct occurrence). + +*(c) Why it relocates (the §2 wall, mechanically reconfirmed).* +Discharging the congruence case needs the sibling’s `+r0+` proved a +_distinct_ occurrence from the exiting one — exactly the +temporal/segment coherence the snapshot env + result type cannot +express. Every closure route tried (`+free_regions ⊆ R_out+`, +`+count_occ_le_l1_m+`, linking the dynamic exit to the static output) +collapses to connecting the dynamic step to the static typing output, +which _is_ preservation. Circular. + +*(d) The §4.8 framing was wrong* and is corrected in the source: the +obstruction is not lambda-rigidity (the witness has no lambda) but +region-count coherence at a region exit. *Net: ADMIT 3 stays open at the +honest count (still 2 outer `+Admitted+`), but is now ONE minimal, +precisely characterised obligation* — the cleanest possible target for +the §3 / §5.1 tropically-graded choreography. That choreographic closure +is the unchanged recommended next step. + +=== 9. Result of experiment 2 (2026 — `+formal/L1ChoreoExperiment2.v+`) + +Experiment 1 (§5/§6, `+L1ChoreoExperiment.v+`) returned *"`relocates`"*, +but its own "`honest gaps`" §2 recorded _why_ and prescribed the next +move: it had *collapsed* the projection onto the scalar predicate +`+expr_no_exit_of_region+` (discarding subterm-relative order) and had +targeted the *clean* `+ERegion rv+` exit (already `+Qed+`) rather than +the genuinely-open *congruence* cases (`+S_Let_Step+` / `+S_App_Step2+` +/ `+S_Pair_Step2+` / `+S_Case_Step+` — _inner pop erased from the outer +result type_). Experiment 2 builds the minimal *non-collapsed* apparatus +that gap asked for and aims it at the congruence boundary. + +*The model.* One region `+rv+`’s lifecycle is a `+Trace+` of events +(`+Open+`/`+Close+`/`+Use+`) _relative to the subterm structure_, run +from an initial balance `+k = cnt rv R+`. The trace keeps the same +open/close balance the scalar `+cnt rv R+` keeps — but, unlike a +snapshot, it keeps the *order*. `+valid k t+` is the local well-nesting +condition (a `+Close+` or `+Use+` requires balance `+>= 1+` at that +point). + +*The decisive result (all `+Qed+`, axiom-free — `+Print Assumptions+` +clean).* The order is exactly enough to separate two configurations the +snapshot _conflates_ at the `+e1+`/`+e2+` boundary: + +* *Internal scope* — the stepping sibling is `+rv+`-net-neutral +(`+final_balance k t1 = k+`): the surviving sibling is coherent at the +original `+k+`, independent of `+t1+`’s internal region activity +(`+congruence_internal_scope_carries+`). +* *Escaping close* — the stepping sibling closes an outer `+rv+` while +the surviving sibling still uses it: *impossible in a coherent trace*. +If the surviving sibling’s dependence is a `+Use+` ordered after `+t1+`, +the post-`+t1+` balance is forced `+>= 1+` +(`+sibling_use_keeps_region_live+`). This is precisely the obligation +`+step_pop_disjoint_from_type_l1+` _relocates to_ +(`+In rv (free_regions T) -> In rv R -> In rv R'+`), and it is proved +here as a pure structural fact — *non-circularly*, with no appeal to +preservation. The snapshot cannot state it because it carries only the +final balance, not the witness that the `+Use+` is _ordered after_ the +stepping sibling. + +*Verdict.* For the congruence cases — where experiment 1 located the +genuinely-open fork — the non-collapsed trace model *closes* the +relocated obligation _in the model_, and reduces the open question to +*one wiring lemma*: that `+has_type_l1+` of a congruence redex induces a +`+valid+` trace at `+k = cnt rv R+` with the surviving sibling’s +`+rv+`-dependence as a trailing `+Use+` (`+wiring_obligation+`, stated +honestly, never faked — mirroring experiment 1’s +`+close_derives_coherence+`). Crucially, experiment 1’s specific failure +mode (the relocated predicate is _false_ at re-entry) *provably does not +recur*: the trace’s `+valid+` is _defined_ to permit re-entry (nested +`+Open+`/`+Close+`, balance `+> 1+`). So the verdict for the congruence +boundary is upgraded from *"`relocates`"* to *"`closes in the trace +model, reduced to one wiring lemma`"*, and the wiring lemma is named as +the precise next target. + +This experiment closes *no* admit and adds *no* axiom (honest count +unchanged); it is the consolidation+sharpening step, not the closure. +The remaining gate to an actual ADMIT-3 closure is `+wiring_obligation+` +— extract a `+valid+` trace from a typing derivation — which, unlike +experiment 1’s dead end, is plausibly a _structural induction_ on the +typing derivation (each `+Close+` / `+Use+` event’s `+>= 1+` premise +mirrors a local `+In r R+` premise in `+T_Region_Active_L1+` / +`+T_Loc_L1+`), not a preservation-strength obligation. diff --git a/formal/L1-ELIMINATOR-FORK.md b/formal/L1-ELIMINATOR-FORK.md deleted file mode 100644 index b1976f87..00000000 --- a/formal/L1-ELIMINATOR-FORK.md +++ /dev/null @@ -1,251 +0,0 @@ - - - -# The eliminator fork — and whether choreographic typing across time segments closes it - -The two admits the tropical re-foundation does **not** dissolve -(`step_pop_disjoint_from_type_l1`, ADMIT 3; `preservation_l1`, ADMIT 4 gated on -it). This note states the challenge precisely, explains why both prior fixes -failed, and assesses the proposal: **type region-liveness choreographically, -across time segments.** Verdict up front: it is the right *shape* — more so than -the grade-in-context patch — but it is unproven and can *relocate* rather than -*close*, so it needs the minimal-counterexample experiment in §5. - -## 1. The challenge, precisely - -Preservation needs: if `R ; G ⊢ e : T -| R' ; G'` and `(μ,R,e) → (μ',R₂,e')`, -then `R₂ ; G ⊢ e' : T -| … `. It fails on a single shape: - -> A **subterm `e_sub` depends on a region `rv`** (its type mentions `rv`, e.g. -> `e_sub : TString rv`), but an **eliminator/context erases `rv` from the -> result type** (`EDrop(·):TUnit`, `Fst`, application, `Let`-body, `Case`-arm), -> and a step **exits `rv`** (`R → R₂`, `rv` gone). Now `e'` still contains -> `e_sub`, which needs `rv` live, but `R₂` no longer has it and `T` never -> showed it. - -Minimal Coq-checked witness: `EDrop (EVar j : TString rv)` at `R = [rv]`. It -types; `expr_strictly_free_of_region rv` holds (no syntactic `rv`); the result -type `Unit` satisfies the Tofte–Talpin premise — yet shrinking `rv` leaves the -inner `EVar j` untypable. - -**The root is a representational mismatch in *time*.** The judgment expresses -region liveness with: -- a **snapshot** `R` (the live set *at one point*), and -- a **result-type-local** view (`free_regions(T)`), - -but the actual obligation is **temporally extended and non-local**: "`rv` must -be live *throughout the evaluation of `e_sub`*". A snapshot cannot say -"throughout"; a result type cannot say "of a subterm whose contribution I -erased". The information needed is *when*, and neither carrier carries time. - -## 2. Why both prior fixes failed (the same wall) - -- **Leaf-rule strengthening (path 3):** put `free_regions(T) ⊆ R` on `T_Var`. - This forces liveness *at the variable use site*. But region-shrink/exit - *wants to remove* `rv` while that very variable is in scope, so the premise - and `region_shrink` **contradict** — the `EDrop` witness is exactly the - contradiction. It moves the snapshot demand to the leaf; it does not add time. -- **Effect-typed lambdas (A′):** makes a *lambda's* footprint type-visible. - Helps only `S_App_Step2`; the witness is a *free variable under an - eliminator*, no lambda involved. It adds type-visibility for one binder - shape; it does not add time. - -Both stay inside the snapshot+result-type ontology. The wall is that ontology. - -## 3. The proposal: liveness as a choreography over time segments - -Replace "liveness = membership in a snapshot" with "liveness = a position in a -**global protocol over time segments**", and recover the per-subterm rule by -**projection**. Concretely: - -- **Time segments.** A region scope `enter rv … exit rv` *is* a segment: the - interval during which `rv` is live. Nesting/re-entry gives nested segments - (depth = the tropical `cnt rv` grade — the two views meet here). -- **Regions as session-typed resources.** Each region is a participant whose - lifecycle is a session `open → use* → close` (exactly a file-handle/typestate - protocol). The whole program is the **choreography**: the interleaving of all - regions' sessions across segments. -- **Local typing = endpoint projection.** A subterm `e_sub` is typed by - *projecting* the choreography onto `e_sub`'s segment. Its dependency on `rv` - is satisfied because the projection places `e_sub` *inside* `rv`'s `open…close` - segment — **not** because `rv` appears in the result type. -- **Preservation = subject reduction for the choreography.** A step advances - the global type to a coherent global type; the snapshot problem dissolves - because the global type carries the *temporal* structure the snapshot lacked. - -The eliminator's "hidden" dependency is then **not hidden**: the global type -records that `e_sub` runs in `rv`'s segment, even though the local/erased result -type does not. That is precisely the missing *when*. - -## 4. Why this is the right shape — and the estate already has both halves - -The dependency is temporal-and-global; choreographies are *the* device for -temporal-global protocols projected to local views. This is a structural match, -not an analogy. And it composes with the tropical re-foundation rather than -competing with it: - -- `tropical-resource-typing` already ships **`TropicalSessionTypes.lean`**. - Session types are the *local projections* of a choreography. So region - liveness becomes a **tropical-graded choreography across time segments**: the - grade (MinPlus `cnt`) carries the *resource quantity* (re-entry depth), the - session/choreographic structure carries the *temporal protocol*. One object, - two readings — the same `echo ≡ tropical ≡ epistemic` unification, now - extended to include the *order of events*, not just their magnitude. -- Division of labour becomes clean: **tropical grade** closes the structural - admits (1, 2 — quantity/carrier); **choreography over segments** is aimed at - the eliminator admit (3 — time). `EchoBridge` already measures L3 residues - into the grade; a choreography adds the missing temporal axis to the same - grade. - -## 5. The honest risks (why "right shape" ≠ "resolved") - -1. **Sequential, not distributed.** Choreographies/session types were built for - *concurrent* systems with communicating participants. The reinterpretation - here — *regions* as participants, *scopes* as segments, region - alloc/use/free as the "communication" — is principled (resource-usage - protocols / typestate are exactly session-shaped) but **non-standard**; it - needs a clean foundation, not an off-the-shelf import. -2. **Projection coherence is where the problem could re-appear.** The whole - method rests on endpoint projection being *coherent* (locals recompose to the - global). The eliminator case might project to a local type that is itself - inconsistent — in which case the difficulty is **relocated** to a coherence - side-condition, not closed. This is the single thing to check first. -3. **Close vs relocate.** Subject reduction for the choreography must genuinely - *carry* "`rv` live throughout `e_sub`'s segment" through the exit step. If it - does, ADMIT 3 closes; if the segment boundary and the exit step don't line up - definitionally, you get a new obligation, not a discharge. -4. **Apparatus cost.** This is a research program (global types, projection, - coherence, subject reduction for the region choreography), not a refactor — - high-reward, high-cost. It should not gate the §`L1-REGION-REFOUNDATION-PLAN` - clean win, which proceeds independently. - -## 6. The deciding experiment (cheap, do this before committing) - -Do **not** build the general theory first. Formulate the *minimal* choreography -that types just `EDrop (EVar j : TString rv)` over two segments — -`S₁ = [enter rv … (EVar j read) …]`, `S₂ = [exit rv …]` — and ask one question: - -> Does the choreography's subject-reduction step for `S_Region_Exit rv` -> transform the global type into a **coherent** global type in which the -> (now-residual) `EVar j` no longer claims `rv`-liveness — *without* a snapshot -> premise? - -- **If yes**, the segment carried the *when*, the projection stayed coherent, - and ADMIT 3 closes choreographically → re-grade `region_liveness` *and* - `step_pop` against the global type; the capstone follows. (This is the path - to **0** admits.) -- **If no — i.e. coherence needs an extra side-condition** — the problem has - *relocated* to that condition. Report it; it is likely the *same* eliminator - obligation in choreographic clothing, and the honest count stays **2** until a - different idea (or a restricted language fragment) lands. - -Either outcome is decisive and cheap, and tells us whether choreographic typing -*resolves* the eliminator fork or merely *renames* it. - -## 7. Verdict - -Choreographic typing across time segments is the **best-shaped** candidate for -ADMIT 3 because it is the only proposal that adds the missing axis — *time* — -rather than re-arranging the snapshot. It also unifies cleanly with the tropical -re-foundation via `TropicalSessionTypes` (a tropically-graded choreography: -quantity × order in one structure). It is **not yet a resolution**: it can -relocate the difficulty into projection coherence, and the sequential-language -foundation is non-standard. The §5/§6 experiment is the inexpensive way to -learn which — and it is the recommended next step on the eliminator fork, run in -parallel with (not blocking) the clean-win carrier refactor. - -## 8. Result of the §6 experiment (2026 — run in Coq) - -The §6 deciding experiment was run against the live judgment. Outcome: the -verdict is the predicted **"no" — it relocates** — but the experiment also -produced a concrete simplification worth keeping. - -**(a) `step_pop_disjoint_from_type_l1` reduces from 11 admits to 1.** Driving -the proof by `step_R_change_shape` (every step leaves `R` fixed, prepends a -fresh region, or removes exactly one) makes all ten eliminator/erasing -congruences trivial — a region free in the result type survives unless the -step exits *that exact* region. The 255-line induction with eleven `admit`s -becomes ~15 lines with one. (Landed; `coqc 8.18.0` + `Print Assumptions` -verified; outer `Admitted` count unchanged.) - -**(b) The single residual obligation, exactly:** the step exits `r0` while -`r0 ∈ free_regions(T)` ⟹ `cnt r0 R ≥ 2` (i.e. `In r0 (remove_first r0 R)`). -Its **direct-exit** sub-case is *vacuous* — a head redex `ERegion r0 v` is -typed by `T_Region_Active_L1`, whose `~In r0 (free_regions T)` premise -contradicts `r0 ∈ free_regions(T)`. The residue is the **congruence-exit** -case (`ERegion r0` under an eliminator; `r0` from a sibling's type — a -distinct occurrence). - -**(c) Why it relocates (the §2 wall, mechanically reconfirmed).** Discharging -the congruence case needs the sibling's `r0` proved a *distinct* occurrence -from the exiting one — exactly the temporal/segment coherence the snapshot -env + result type cannot express. Every closure route tried -(`free_regions ⊆ R_out`, `count_occ_le_l1_m`, linking the dynamic exit to the -static output) collapses to connecting the dynamic step to the static typing -output, which *is* preservation. Circular. - -**(d) The §4.8 framing was wrong** and is corrected in the source: the -obstruction is not lambda-rigidity (the witness has no lambda) but -region-count coherence at a region exit. **Net: ADMIT 3 stays open at the -honest count (still 2 outer `Admitted`), but is now ONE minimal, precisely -characterised obligation** — the cleanest possible target for the §3 / §5.1 -tropically-graded choreography. That choreographic closure is the -unchanged recommended next step. - -## 9. Result of experiment 2 (2026 — `formal/L1ChoreoExperiment2.v`) - -Experiment 1 (§5/§6, `L1ChoreoExperiment.v`) returned **"relocates"**, but its -own "honest gaps" §2 recorded *why* and prescribed the next move: it had -**collapsed** the projection onto the scalar predicate -`expr_no_exit_of_region` (discarding subterm-relative order) and had targeted -the **clean** `ERegion rv` exit (already `Qed`) rather than the genuinely-open -**congruence** cases (`S_Let_Step` / `S_App_Step2` / `S_Pair_Step2` / -`S_Case_Step` — *inner pop erased from the outer result type*). Experiment 2 -builds the minimal **non-collapsed** apparatus that gap asked for and aims it at -the congruence boundary. - -**The model.** One region `rv`'s lifecycle is a `Trace` of events -(`Open`/`Close`/`Use`) *relative to the subterm structure*, run from an initial -balance `k = cnt rv R`. The trace keeps the same open/close balance the scalar -`cnt rv R` keeps — but, unlike a snapshot, it keeps the **order**. `valid k t` -is the local well-nesting condition (a `Close` or `Use` requires balance `>= 1` -at that point). - -**The decisive result (all `Qed`, axiom-free — `Print Assumptions` clean).** -The order is exactly enough to separate two configurations the snapshot -*conflates* at the `e1`/`e2` boundary: - -- **Internal scope** — the stepping sibling is `rv`-net-neutral - (`final_balance k t1 = k`): the surviving sibling is coherent at the original - `k`, independent of `t1`'s internal region activity - (`congruence_internal_scope_carries`). -- **Escaping close** — the stepping sibling closes an outer `rv` while the - surviving sibling still uses it: **impossible in a coherent trace**. If the - surviving sibling's dependence is a `Use` ordered after `t1`, the post-`t1` - balance is forced `>= 1` (`sibling_use_keeps_region_live`). This is precisely - the obligation `step_pop_disjoint_from_type_l1` *relocates to* - (`In rv (free_regions T) -> In rv R -> In rv R'`), and it is proved here as a - pure structural fact — **non-circularly**, with no appeal to preservation. - The snapshot cannot state it because it carries only the final balance, not - the witness that the `Use` is *ordered after* the stepping sibling. - -**Verdict.** For the congruence cases — where experiment 1 located the -genuinely-open fork — the non-collapsed trace model **closes** the relocated -obligation *in the model*, and reduces the open question to **one wiring -lemma**: that `has_type_l1` of a congruence redex induces a `valid` trace at -`k = cnt rv R` with the surviving sibling's `rv`-dependence as a trailing -`Use` (`wiring_obligation`, stated honestly, never faked — mirroring -experiment 1's `close_derives_coherence`). Crucially, experiment 1's specific -failure mode (the relocated predicate is *false* at re-entry) **provably does -not recur**: the trace's `valid` is *defined* to permit re-entry (nested -`Open`/`Close`, balance `> 1`). So the verdict for the congruence boundary is -upgraded from **"relocates"** to **"closes in the trace model, reduced to one -wiring lemma"**, and the wiring lemma is named as the precise next target. - -This experiment closes **no** admit and adds **no** axiom (honest count -unchanged); it is the consolidation+sharpening step, not the closure. The -remaining gate to an actual ADMIT-3 closure is `wiring_obligation` — extract a -`valid` trace from a typing derivation — which, unlike experiment 1's dead end, -is plausibly a *structural induction* on the typing derivation (each `Close` / -`Use` event's `>= 1` premise mirrors a local `In r R` premise in -`T_Region_Active_L1` / `T_Loc_L1`), not a preservation-strength obligation. diff --git a/formal/L1-REGION-REFOUNDATION-PLAN.adoc b/formal/L1-REGION-REFOUNDATION-PLAN.adoc new file mode 100644 index 00000000..928f07d0 --- /dev/null +++ b/formal/L1-REGION-REFOUNDATION-PLAN.adoc @@ -0,0 +1,316 @@ +== L1 region re-foundation — the clean-win Coq work-plan + +*Scope.* This plan covers the two admits that the tropical re-foundation +dissolves cleanly: `+region_shrink_preserves_typing_l1_gen_m+` (ADMIT 1) +and the _false_ `+region_liveness_at_split_l1_gen+` (ADMIT 2). It does +*not* cover `+step_pop_disjoint_from_type_l1+` (ADMIT 3, the eliminator +fork) or the capstone `+preservation_l1+` (ADMIT 4, gated on 3) — those +are the separate research problem (see `+L1-ELIMINATOR-FORK.md+`). + +Evidence base: the isomorphism validation against +`+tropical-resource-typing+` +(`+docs/applications/ephapax-l1-regions.adoc+` in that repo). The +carrier `+list region_name+` is a `+region_name →₀ ℕ+` grade vector +wearing a list costume; the admits live where the costume tears. + +=== ⚠️ REVISION 2026-06-16 — diagnosis correction (read before Step 0) + +A code-level re-audit of `+Semantics_L1.v+` / `+TypingL1.v+` / +`+Syntax.v+` found the original plan below rests on a *misdiagnosis*. +Step 0 (a single global edit at `+Syntax.v:867+`) is *not viable* and +the carrier is *not* the actual blocker. Corrected findings: + +[arabic] +. *The carrier is shared with the fenced legacy judgment.* +`+region_env := list region_name+` (`+Syntax.v:867+`) is used by the +legacy `+Typing.v+` / `+Semantics.v+` (90 `+region_env+` refs, 88 +list-cons, 151 `+In r R+`) *and* by `+Counterexample.v+` (which couples +to _both_ carriers: `+remove_first+` ×4 _and_ `+remove_first_L1+` ×4). A +_global_ Step-0 edit would rewrite the fenced legacy judgment and risk +the `+Counterexample.v+` regression theorems (CLAUDE.md fences #2/#5). +Only an *L1-only carrier fork* (a new `+region_env_l1+`, legacy left on +the list) is admissible — not the "`single highest-leverage edit`" +framing. +. *The count/multiset view already exists over the list.* `+cnt+` +(`+:242+`), `+remove_first_comm+` (`+:205+`, Qed), +`+remove_first_eq_l1+` (`+:132+`, Qed), +`+remove_first_L1_count_eq_self+` / `+_count_other+`, +`+count_occ_le_l1_m+` (`+:279+`) are all already proved. Step 1/Step 2 +("`introduce the count operations and coordinate lemmas`") are *already +done* as a _view_ over the list. The admits are therefore *not* blocked +by the absence of multiset reasoning. +. *The "`list costume tears`" story is wrong; the blocker is +proof-structural.* The `+:520+` / `+:565+` comments ("`Hfree is True +(irrelevant)`") are *stale* — leftovers from the _weak_ predicate before +the 2026-05-28 strict migration. Under the strict predicate, +`+expr_strictly_free_of_region r (ERegion r e)+` *descends +unconditionally* (`+Syntax.v:268-270+`), so in the `+rr = r+` shadowed +case `+Hfree : expr_strictly_free_of_region r e+` is _informative_ and +`+IHHt r Hfree+` is available — the exact tool the _working_ descend +case already uses at `+:558+`. The admit case has the same tools as the +case that compiles; it is missing a case-split, not a carrier. +. *The real obstacle in ADMIT 1* (`+T_Region_Active_L1+` / `+_Echo+`, +`+rr = r+`, `+:576+` / `+:646+`) is a case-split on `+cnt r R+`: +* `+cnt r R ≥ 2+` → `+r+` survives `+remove_first r R+`; close via +`+T_Region_Active_L1+` on the IH-shrunk body +(`+IHHt r Hfree : remove_first r R ⊢ e -| remove_first r R_body+`). +Needs a *count-preservation-under-strict-freedom* fact: a body strictly +free of `+r+` leaves `+cnt r+` unchanged (`+cnt r R_body = cnt r R+`), +so the rule’s `+In r R_body'+` premise holds. Output reconciles via +`+remove_first_eq_l1+`. +* `+cnt r R = 1+` → `+r+` is _dead_ after the shrink; the goal must be +retyped with `+T_Region_L1+` (fresh), whose body lives at +`+r :: remove_first r R+`. This needs an *L1 +region-permutation-transport lemma* — the analog of the legacy +`+region_env_perm_typing+` (`+Semantics.v:3112+`), i.e. *option (a)* +named in the case’s own comment. This is the genuinely missing +infrastructure. +. *What the carrier fork actually buys — and its cost.* With a count-map +carrier and `+funext+`, `+enter r (exit r R) = R+` _as functions_ +whenever `+cnt r R ≥ 1+`, which dissolves the `+cnt r R = 1+` +permutation obligation (the fresh rule’s body input becomes literally +`+R+`, so the original body derivation applies). That is the true +mechanism — *not* `+exit_comm; lia+`, which only handles the arithmetic, +not the rule-selection flip. The price is `+functional_extensionality+` +in the L1 trusted base (visible in `+Print Assumptions+`), or a +finite-map *setoid* + `+Proper+` instances on the ~1000-line +`+has_type_l1+` inductive to stay axiom-free. +. *ADMIT 2 is representation-independent.* +`+region_liveness_at_split_l1_gen+` (`+:1942+`) is a _false_ lemma; +closing it is restatement (`+expr_no_exit_of_region+` side condition) + +the ~13-site audit (Step 5/5a below), regardless of carrier. The carrier +choice does not touch it. + +==== CORRECTION 2026-06-16 (later same day) — path (A) is NOT viable + +I first recommended (A) "`axiom-free, list carrier, port a transport +lemma`" and the owner selected it. *On execution this was found to be a +dead end*, for a reason already documented in the file’s own design note +(`+Semantics_L1.v:366-414+`) and independently re-derived by dumping the +exact admit goal in `+coqc+`: + +.... +GOAL (T_Region_Active_L1, rr = r): + remove_first r R ; G |=L1[m] ERegion r e : T -| remove_first r (remove_first_L1 r R_body) ; G' +.... + +The `+cnt r R = 1+` sub-case (r dies after the shrink) must re-type +`+ERegion r e+` with the *fresh* rule `+T_Region_L1+`, whose body lives +at `+r :: remove_first r R+`. Transporting the body derivation there +yields an output only *multiset-equal* to `+R_body+`; but the rule pops +the *first list occurrence* (`+remove_first_L1+`), so the goal’s output +is a *specific list*, and `+remove_first_L1 r+` of two multiset-equal +lists are themselves only multiset-equal (`+[a;r;b]+` vs `+[b;r;a]+` → +`+[a;b]+` vs `+[b;a]+`). The design note at `+:374-390+` states this +exactly: _"`the output depends on the list structure, not just its +membership … set-equivalence (and even multiset-equivalence in some +sub-sub-cases) does not provide [list-structure agreement].`"_ A +count/multiset transport lemma — the keystone (A) needed — is therefore +*insufficient by construction*. Value rules also pin `+R_out = R_in+` +_as a list_, so the judgment is not `+Permutation+`-`+Proper+` either: +you cannot bolt order-insensitivity on as a lemma. + +*Conclusion: closing ADMIT 1 requires removing the first-occurrence-list +dependence itself.* That is the owner’s original committed route. The +viable, axiom-free realizations: + +* *(C) count-map / finite-map carrier + setoid.* L1-only +`+region_env_l1+` (legacy stays on the list). Order-free by +construction; `+enter∘exit = id+` dissolves the `+cnt = 1+` corner. +Axiom-free via a finite-map setoid + `+Proper+` instances (NOT +`+funext+`). Largest refactor; *also the first step of the +L1≡L3≡tropical + choreographic unification* +(`+project_ephapax_choreographic_tropical_foundation+`). +* *(b-sorted) canonical sorted-list operations.* Keep +`+region_env := list+`, but make `+remove_first_L1+` / the region rules +operate on an _insertion-sorted_ canonical form so list-structure ≡ +multiset and equality is decidable list equality (no funext, no setoid). +Medium refactor; does not by itself advance the tropical unification. +* *(B) count-map carrier + `+funext+`.* Smallest proof, but *+1 axiom* +(`+functional_extensionality+`). Conflicts with the estate axiom-freedom +posture. + +*Re-surfaced to owner 2026-06-16:* I was wrong to bill (A) as a cheap +close; recommend *(C)* (axiom-free, removes first-occurrence dependence, +serves the broader unification) or *(b-sorted)* if minimal churn is +preferred over the unification payoff. ADMIT 2 (`+region_liveness+`) is +independent of all this — restatement + 13-site audit, doable under any +option. + +''''' + +=== Step 0 — the carrier change (single highest-leverage edit) [SUPERSEDED — see REVISION above] + +`+Syntax.v:867+`: + +[source,coq] +---- +(* before *) +Definition region_env := list region_name. + +(* after *) +Definition region_env := region_name -> nat. (* cnt r R = re-entry depth of r; 0 = dead *) +---- + +A total function is preferred over a `+FMap+`/`+list (region_name*nat)+` +for the proofs: every region operation becomes a _pointwise `+nat+` +fact_, with no finiteness obligation and no canonical-form/sortedness +bookkeeping. Equality is `+funext+` (or work up to pointwise equality +with a `+region_env_eq+` setoid to avoid axioms — see Step 4 note). + +=== Step 1 — replace the four operations + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Old (list) |New (count-map) |Definition +|`+In r R+` |`+live R r+` |`+R r >= 1+` + +|`+r :: R+` (enter) |`+enter r R+` +|`+fun r' => if String.eqb r' r then S (R r') else R r'+` + +|`+remove_first r R+` (exit) |`+exit r R+` +|`+fun r' => if String.eqb r' r then pred (R r') else R r'+` + +|`+count_occ _ R r+` |`+cnt r R+` |`+R r+` +|=== + +`+remove_first_L1+` (the TypingL1 copy) collapses into `+exit+` — the +two are now literally the same function, removing the list/multiset +double-bookkeeping that PRESERVATION-DESIGN §5.1 flagged. + +=== Step 2 — the coordinate lemmas (all `+nat+`-trivial, all become `+Qed+`) + +These replace the list lemmas (`+remove_first_comm+`, +`+count_occ_le_l1_m+`, `+remove_first_L1_count_eq_self+`, +`+_count_other+`). Each is one `+unfold+` + `+funext+` + +`+destruct (String.eqb …)+` + `+lia+`: + +[source,coq] +---- +Lemma exit_comm : forall r1 r2 R, exit r1 (exit r2 R) = exit r2 (exit r1 R). +Lemma cnt_exit_self : forall r R, cnt r (exit r R) = pred (cnt r R). +Lemma cnt_exit_other: forall r r' R, r <> r' -> cnt r' (exit r R) = cnt r' R. +Lemma cnt_enter_self: forall r R, cnt r (enter r R) = S (cnt r R). +Lemma cnt_le_exit : forall r r' R, cnt r' (exit r R) <= cnt r' R. (* monotonicity *) +Lemma live_exit_other: forall r r' R, r <> r' -> live R r' -> live (exit r R) r'. +---- + +`+exit_comm+` is the discharge for ADMIT 1’s residual sub-cases (:572, +:642): the `+rr = r+` shadowed `+T_Region_Active+` case is now +`+pred (pred (R r))+` either way — order-free by construction. +`+cnt_le_exit+` is grade-monotonicity = `+count_occ_le_l1_m+` = the +tropical `+subsumption_transports+` instance. + +=== Step 3 — re-thread the judgment + +In `+TypingL1.v+` rewrite the region rules’ premises/outputs in the new +vocabulary (mechanical): + +* `+T_Region_L1+` / `+T_Region_Active_L1+`: `+In r R+` → `+live R r+`; +output `+remove_first_L1 r R_body+` → `+exit r R_body+`; the +Tofte–Talpin premise `+~ In r (free_regions T)+` is unchanged (it is +about the _type_, not the env). +* `+T_Loc_L1+` / `+T_StringNew_L1+`: `+In r R+` → `+live R r+`. +* Every step rule in `+Semantics_L1.v+` / `+Semantics.v+` touching +`+r :: R+` or `+remove_first r R+` → `+enter+`/`+exit+`. + +This is the bulk of the diff (every +`+In+`/`+remove_first+`/`+count_occ+` use across `+TypingL1.v+`, +`+Semantics_L1.v+`, `+Counterexample_L2*.v+`), but it is find-and- +replace plus re-running tactics; the _content_ of the proofs is +unchanged except where it was fighting the list (Step 4). + +=== Step 4 — ADMIT 1 closes + +`+region_shrink_preserves_typing_l1_gen_m+` (:441). The two internal +admits (:572,:642) were the shadowed `+T_Region_Active_L1{,_Echo}+` +`+rr = r+` case where list `+remove_first+` ordering and +`+remove_first_L1+` disagreed. With `+exit+` and `+exit_comm+`, the goal +is `+pred+`-arithmetic on the `+rr+` coordinate plus `+cnt_exit_other+` +on the rest. Close with `+exit_comm; cnt_exit_*; lia+`. *Qed.* + +____ +Axiom note: if `+funext+` is unwanted in the trusted base, carry a +pointwise `+region_env_eq+` and prove the rules respect it (a `+Proper+` +instance). Mild extra work; keeps `+Print Assumptions+` axiom-free. +Decide before Step 0. +____ + +=== Step 5 — ADMIT 2 repaired (the false lemma) + +`+region_liveness_at_split_l1_gen+` (:1904) is *false as written* +(`+In rv R -> In rv R'+`; witness `+ERegion rv (EI32 5)+` at `+R=[rv]+` +pops the only `+rv+`). Restate as the _graded, conditional_ form and +prove from monotonicity: + +[source,coq] +---- +(* replaces the false lemma *) +Lemma region_liveness_graded_l1 : + forall m R G e T R' G' rv, + R ; G |=L1[m] e : T -| R' ; G' -> + expr_no_exit_of_region rv e -> (* e performs no S_Region_Exit on rv *) + live R rv -> live R' rv. +Proof. + (* cnt rv R' = cnt rv R when e exits rv zero times (from cnt_le_exit + the + exit-count being 0); live = cnt >= 1 is then preserved. *) +Qed. +---- + +`+expr_no_exit_of_region+` is the honest side-condition the _false_ +version silently dropped. If a usable predicate already exists, reuse +`+expr_strictly_free_of_region+` (the region-shrink hypothesis is the +same shape). + +==== Step 5a — call-site audit (the real work in ADMIT 2) + +The old `+region_liveness_at_split_l1_gen+` axiom is consumed at ~13 +sites in `+subst_typing_gen_l1*+` (Semantics_L1.v ~:2140–:2291). Each +must be re-checked: + +[arabic] +. *Served cleanly*: the site already steps a sub-expression provably +free of `+rv+`-exits (it usually carries +`+expr_strictly_free_of_region rv …+`). Thread +`+expr_no_exit_of_region+` and apply `+region_liveness_graded_l1+`. +Expect the majority here. +. *Needs the threaded side-condition*: add `+expr_no_exit_of_region rv+` +to the _caller’s_ hypotheses and discharge it from that caller’s +context. +. *Genuinely unconditional*: any site that _needed_ +`+In rv R -> In rv R'+` with no no-exit guarantee was relying on a false +statement — flag it; it is really an ADMIT-3 instance (the eliminator +fork) in disguise, not closable here. + +Produce a table (site :: class :: discharge) before editing; classes +(1)/(2) close ADMIT 2, class (3) sites get moved to the eliminator-fork +backlog. + +=== Step 6 — verify + +* `+make -f build.mk clean && make -f build.mk+` exits 0. +* `+region_shrink_preserves_typing_l1_gen_m+` and +`+region_liveness_graded_l1+` end in `+Qed+`; the old false lemma is +deleted (not `+Admitted+`). +* `+Print Assumptions+` for both: no `+region_*+` self-axioms among +them; ideally no new axioms (modulo the `+funext+`/setoid decision in +Step 4). +* `+Counterexample.v+`, `+Counterexample_L2*.v+` still compile +(strengthening only shrinks the typable set; untypability witnesses are +preserved). +* Net live-admit count: *4 → 2* (`+step_pop+` + the gated +`+preservation_l1+` remain), and *zero false lemmas* (the soundness +smell is gone). + +=== What this buys / does not buy + +* *Buys*: ADMIT 1 + ADMIT 2 closed; the false lemma replaced by a true +one; `+remove_first_L1+`/`+remove_first+` double-bookkeeping eliminated; +the carrier is now the proven tropical/ℕ-multiset grade (re-foundation +done, L1≡L3 wiring via EchoBridge now possible as an additive +follow-on). +* *Does not buy*: closure of `+step_pop+` (ADMIT 3) or +`+preservation_l1+` (ADMIT 4). Those are the eliminator fork — a +value/eliminator depending on a region absent from its _result type_ — +and the naïve `+T_Var+` premise that would close them is *proven false*. +See `+L1-ELIMINATOR-FORK.md+`. diff --git a/formal/L1-REGION-REFOUNDATION-PLAN.md b/formal/L1-REGION-REFOUNDATION-PLAN.md deleted file mode 100644 index 5bb65aa7..00000000 --- a/formal/L1-REGION-REFOUNDATION-PLAN.md +++ /dev/null @@ -1,279 +0,0 @@ - - - -# L1 region re-foundation — the clean-win Coq work-plan - -**Scope.** This plan covers the two admits that the tropical re-foundation -dissolves cleanly: `region_shrink_preserves_typing_l1_gen_m` (ADMIT 1) and the -*false* `region_liveness_at_split_l1_gen` (ADMIT 2). It does **not** cover -`step_pop_disjoint_from_type_l1` (ADMIT 3, the eliminator fork) or the capstone -`preservation_l1` (ADMIT 4, gated on 3) — those are the separate research -problem (see `L1-ELIMINATOR-FORK.md`). - -Evidence base: the isomorphism validation against `tropical-resource-typing` -(`docs/applications/ephapax-l1-regions.adoc` in that repo). The carrier -`list region_name` is a `region_name →₀ ℕ` grade vector wearing a list costume; -the admits live where the costume tears. - -## ⚠️ REVISION 2026-06-16 — diagnosis correction (read before Step 0) - -A code-level re-audit of `Semantics_L1.v` / `TypingL1.v` / `Syntax.v` found -the original plan below rests on a **misdiagnosis**. Step 0 (a single global -edit at `Syntax.v:867`) is **not viable** and the carrier is **not** the -actual blocker. Corrected findings: - -1. **The carrier is shared with the fenced legacy judgment.** - `region_env := list region_name` (`Syntax.v:867`) is used by the legacy - `Typing.v` / `Semantics.v` (90 `region_env` refs, 88 list-cons, 151 - `In r R`) **and** by `Counterexample.v` (which couples to *both* carriers: - `remove_first` ×4 *and* `remove_first_L1` ×4). A *global* Step-0 edit would - rewrite the fenced legacy judgment and risk the `Counterexample.v` - regression theorems (CLAUDE.md fences #2/#5). Only an **L1-only carrier - fork** (a new `region_env_l1`, legacy left on the list) is admissible — not - the "single highest-leverage edit" framing. - -2. **The count/multiset view already exists over the list.** `cnt` (`:242`), - `remove_first_comm` (`:205`, Qed), `remove_first_eq_l1` (`:132`, Qed), - `remove_first_L1_count_eq_self` / `_count_other`, `count_occ_le_l1_m` - (`:279`) are all already proved. Step 1/Step 2 ("introduce the count - operations and coordinate lemmas") are **already done** as a *view* over the - list. The admits are therefore **not** blocked by the absence of multiset - reasoning. - -3. **The "list costume tears" story is wrong; the blocker is proof-structural.** - The `:520` / `:565` comments ("Hfree is True (irrelevant)") are **stale** — - leftovers from the *weak* predicate before the 2026-05-28 strict migration. - Under the strict predicate, `expr_strictly_free_of_region r (ERegion r e)` - **descends unconditionally** (`Syntax.v:268-270`), so in the `rr = r` - shadowed case `Hfree : expr_strictly_free_of_region r e` is *informative* - and `IHHt r Hfree` is available — the exact tool the *working* descend case - already uses at `:558`. The admit case has the same tools as the case that - compiles; it is missing a case-split, not a carrier. - -4. **The real obstacle in ADMIT 1** (`T_Region_Active_L1` / `_Echo`, `rr = r`, - `:576` / `:646`) is a case-split on `cnt r R`: - - `cnt r R ≥ 2` → `r` survives `remove_first r R`; close via - `T_Region_Active_L1` on the IH-shrunk body - (`IHHt r Hfree : remove_first r R ⊢ e -| remove_first r R_body`). Needs a - **count-preservation-under-strict-freedom** fact: a body strictly free of - `r` leaves `cnt r` unchanged (`cnt r R_body = cnt r R`), so the rule's - `In r R_body'` premise holds. Output reconciles via `remove_first_eq_l1`. - - `cnt r R = 1` → `r` is *dead* after the shrink; the goal must be retyped - with `T_Region_L1` (fresh), whose body lives at `r :: remove_first r R`. - This needs an **L1 region-permutation-transport lemma** — the analog of - the legacy `region_env_perm_typing` (`Semantics.v:3112`), i.e. **option - (a)** named in the case's own comment. This is the genuinely missing - infrastructure. - -5. **What the carrier fork actually buys — and its cost.** With a count-map - carrier and `funext`, `enter r (exit r R) = R` *as functions* whenever - `cnt r R ≥ 1`, which dissolves the `cnt r R = 1` permutation obligation - (the fresh rule's body input becomes literally `R`, so the original body - derivation applies). That is the true mechanism — **not** `exit_comm; lia`, - which only handles the arithmetic, not the rule-selection flip. The price is - `functional_extensionality` in the L1 trusted base (visible in - `Print Assumptions`), or a finite-map **setoid** + `Proper` instances on the - ~1000-line `has_type_l1` inductive to stay axiom-free. - -6. **ADMIT 2 is representation-independent.** `region_liveness_at_split_l1_gen` - (`:1942`) is a *false* lemma; closing it is restatement - (`expr_no_exit_of_region` side condition) + the ~13-site audit (Step 5/5a - below), regardless of carrier. The carrier choice does not touch it. - -### CORRECTION 2026-06-16 (later same day) — path (A) is NOT viable - -I first recommended (A) "axiom-free, list carrier, port a transport lemma" and -the owner selected it. **On execution this was found to be a dead end**, for a -reason already documented in the file's own design note (`Semantics_L1.v:366-414`) -and independently re-derived by dumping the exact admit goal in `coqc`: - -``` -GOAL (T_Region_Active_L1, rr = r): - remove_first r R ; G |=L1[m] ERegion r e : T -| remove_first r (remove_first_L1 r R_body) ; G' -``` - -The `cnt r R = 1` sub-case (r dies after the shrink) must re-type `ERegion r e` -with the **fresh** rule `T_Region_L1`, whose body lives at `r :: remove_first r R`. -Transporting the body derivation there yields an output only **multiset-equal** -to `R_body`; but the rule pops the **first list occurrence** -(`remove_first_L1`), so the goal's output is a **specific list**, and -`remove_first_L1 r` of two multiset-equal lists are themselves only -multiset-equal (`[a;r;b]` vs `[b;r;a]` → `[a;b]` vs `[b;a]`). The design note at -`:374-390` states this exactly: *"the output depends on the list structure, not -just its membership … set-equivalence (and even multiset-equivalence in some -sub-sub-cases) does not provide [list-structure agreement]."* A count/multiset -transport lemma — the keystone (A) needed — is therefore **insufficient by -construction**. Value rules also pin `R_out = R_in` *as a list*, so the judgment -is not `Permutation`-`Proper` either: you cannot bolt order-insensitivity on as a -lemma. - -**Conclusion: closing ADMIT 1 requires removing the first-occurrence-list -dependence itself.** That is the owner's original committed route. The viable, -axiom-free realizations: - -- **(C) count-map / finite-map carrier + setoid.** L1-only `region_env_l1` - (legacy stays on the list). Order-free by construction; `enter∘exit = id` - dissolves the `cnt = 1` corner. Axiom-free via a finite-map setoid + `Proper` - instances (NOT `funext`). Largest refactor; **also the first step of the - L1≡L3≡tropical + choreographic unification** (`project_ephapax_choreographic_tropical_foundation`). -- **(b-sorted) canonical sorted-list operations.** Keep `region_env := list`, - but make `remove_first_L1` / the region rules operate on an *insertion-sorted* - canonical form so list-structure ≡ multiset and equality is decidable list - equality (no funext, no setoid). Medium refactor; does not by itself advance - the tropical unification. -- **(B) count-map carrier + `funext`.** Smallest proof, but **+1 axiom** - (`functional_extensionality`). Conflicts with the estate axiom-freedom posture. - -**Re-surfaced to owner 2026-06-16:** I was wrong to bill (A) as a cheap close; -recommend **(C)** (axiom-free, removes first-occurrence dependence, serves the -broader unification) or **(b-sorted)** if minimal churn is preferred over the -unification payoff. ADMIT 2 (`region_liveness`) is independent of all this — -restatement + 13-site audit, doable under any option. - ---- - -## Step 0 — the carrier change (single highest-leverage edit) [SUPERSEDED — see REVISION above] - -`Syntax.v:867`: - -```coq -(* before *) -Definition region_env := list region_name. - -(* after *) -Definition region_env := region_name -> nat. (* cnt r R = re-entry depth of r; 0 = dead *) -``` - -A total function is preferred over a `FMap`/`list (region_name*nat)` for the -proofs: every region operation becomes a *pointwise `nat` fact*, with no -finiteness obligation and no canonical-form/sortedness bookkeeping. Equality is -`funext` (or work up to pointwise equality with a `region_env_eq` setoid to -avoid axioms — see Step 4 note). - -## Step 1 — replace the four operations - -| Old (list) | New (count-map) | Definition | -|---|---|---| -| `In r R` | `live R r` | `R r >= 1` | -| `r :: R` (enter) | `enter r R` | `fun r' => if String.eqb r' r then S (R r') else R r'` | -| `remove_first r R` (exit) | `exit r R` | `fun r' => if String.eqb r' r then pred (R r') else R r'` | -| `count_occ _ R r` | `cnt r R` | `R r` | - -`remove_first_L1` (the TypingL1 copy) collapses into `exit` — the two are now -literally the same function, removing the list/multiset double-bookkeeping that -PRESERVATION-DESIGN §5.1 flagged. - -## Step 2 — the coordinate lemmas (all `nat`-trivial, all become `Qed`) - -These replace the list lemmas (`remove_first_comm`, `count_occ_le_l1_m`, -`remove_first_L1_count_eq_self`, `_count_other`). Each is one `unfold` + -`funext` + `destruct (String.eqb …)` + `lia`: - -```coq -Lemma exit_comm : forall r1 r2 R, exit r1 (exit r2 R) = exit r2 (exit r1 R). -Lemma cnt_exit_self : forall r R, cnt r (exit r R) = pred (cnt r R). -Lemma cnt_exit_other: forall r r' R, r <> r' -> cnt r' (exit r R) = cnt r' R. -Lemma cnt_enter_self: forall r R, cnt r (enter r R) = S (cnt r R). -Lemma cnt_le_exit : forall r r' R, cnt r' (exit r R) <= cnt r' R. (* monotonicity *) -Lemma live_exit_other: forall r r' R, r <> r' -> live R r' -> live (exit r R) r'. -``` - -`exit_comm` is the discharge for ADMIT 1's residual sub-cases (:572, :642): the -`rr = r` shadowed `T_Region_Active` case is now `pred (pred (R r))` either way — -order-free by construction. `cnt_le_exit` is grade-monotonicity = -`count_occ_le_l1_m` = the tropical `subsumption_transports` instance. - -## Step 3 — re-thread the judgment - -In `TypingL1.v` rewrite the region rules' premises/outputs in the new -vocabulary (mechanical): - -- `T_Region_L1` / `T_Region_Active_L1`: `In r R` → `live R r`; - output `remove_first_L1 r R_body` → `exit r R_body`; - the Tofte–Talpin premise `~ In r (free_regions T)` is unchanged (it is about - the *type*, not the env). -- `T_Loc_L1` / `T_StringNew_L1`: `In r R` → `live R r`. -- Every step rule in `Semantics_L1.v` / `Semantics.v` touching `r :: R` or - `remove_first r R` → `enter`/`exit`. - -This is the bulk of the diff (every `In`/`remove_first`/`count_occ` use across -`TypingL1.v`, `Semantics_L1.v`, `Counterexample_L2*.v`), but it is find-and- -replace plus re-running tactics; the *content* of the proofs is unchanged -except where it was fighting the list (Step 4). - -## Step 4 — ADMIT 1 closes - -`region_shrink_preserves_typing_l1_gen_m` (:441). The two internal admits -(:572,:642) were the shadowed `T_Region_Active_L1{,_Echo}` `rr = r` case where -list `remove_first` ordering and `remove_first_L1` disagreed. With `exit` and -`exit_comm`, the goal is `pred`-arithmetic on the `rr` coordinate plus -`cnt_exit_other` on the rest. Close with `exit_comm; cnt_exit_*; lia`. **Qed.** - -> Axiom note: if `funext` is unwanted in the trusted base, carry a pointwise -> `region_env_eq` and prove the rules respect it (a `Proper` instance). Mild -> extra work; keeps `Print Assumptions` axiom-free. Decide before Step 0. - -## Step 5 — ADMIT 2 repaired (the false lemma) - -`region_liveness_at_split_l1_gen` (:1904) is **false as written** -(`In rv R -> In rv R'`; witness `ERegion rv (EI32 5)` at `R=[rv]` pops the only -`rv`). Restate as the *graded, conditional* form and prove from monotonicity: - -```coq -(* replaces the false lemma *) -Lemma region_liveness_graded_l1 : - forall m R G e T R' G' rv, - R ; G |=L1[m] e : T -| R' ; G' -> - expr_no_exit_of_region rv e -> (* e performs no S_Region_Exit on rv *) - live R rv -> live R' rv. -Proof. - (* cnt rv R' = cnt rv R when e exits rv zero times (from cnt_le_exit + the - exit-count being 0); live = cnt >= 1 is then preserved. *) -Qed. -``` - -`expr_no_exit_of_region` is the honest side-condition the *false* version -silently dropped. If a usable predicate already exists, reuse -`expr_strictly_free_of_region` (the region-shrink hypothesis is the same shape). - -### Step 5a — call-site audit (the real work in ADMIT 2) - -The old `region_liveness_at_split_l1_gen` axiom is consumed at ~13 sites in -`subst_typing_gen_l1*` (Semantics_L1.v ~:2140–:2291). Each must be re-checked: - -1. **Served cleanly**: the site already steps a sub-expression provably free of - `rv`-exits (it usually carries `expr_strictly_free_of_region rv …`). Thread - `expr_no_exit_of_region` and apply `region_liveness_graded_l1`. Expect the - majority here. -2. **Needs the threaded side-condition**: add `expr_no_exit_of_region rv` to - the *caller's* hypotheses and discharge it from that caller's context. -3. **Genuinely unconditional**: any site that *needed* `In rv R -> In rv R'` - with no no-exit guarantee was relying on a false statement — flag it; it is - really an ADMIT-3 instance (the eliminator fork) in disguise, not closable - here. - -Produce a table (site :: class :: discharge) before editing; classes (1)/(2) -close ADMIT 2, class (3) sites get moved to the eliminator-fork backlog. - -## Step 6 — verify - -- `make -f build.mk clean && make -f build.mk` exits 0. -- `region_shrink_preserves_typing_l1_gen_m` and `region_liveness_graded_l1` end - in `Qed`; the old false lemma is deleted (not `Admitted`). -- `Print Assumptions` for both: no `region_*` self-axioms among them; ideally no - new axioms (modulo the `funext`/setoid decision in Step 4). -- `Counterexample.v`, `Counterexample_L2*.v` still compile (strengthening only - shrinks the typable set; untypability witnesses are preserved). -- Net live-admit count: **4 → 2** (`step_pop` + the gated `preservation_l1` - remain), and **zero false lemmas** (the soundness smell is gone). - -## What this buys / does not buy - -- **Buys**: ADMIT 1 + ADMIT 2 closed; the false lemma replaced by a true one; - `remove_first_L1`/`remove_first` double-bookkeeping eliminated; the carrier is - now the proven tropical/ℕ-multiset grade (re-foundation done, L1≡L3 wiring via - EchoBridge now possible as an additive follow-on). -- **Does not buy**: closure of `step_pop` (ADMIT 3) or `preservation_l1` - (ADMIT 4). Those are the eliminator fork — a value/eliminator depending on a - region absent from its *result type* — and the naïve `T_Var` premise that - would close them is **proven false**. See `L1-ELIMINATOR-FORK.md`. diff --git a/formal/L4-DYADIC.adoc b/formal/L4-DYADIC.adoc new file mode 100644 index 00000000..dd528fe8 --- /dev/null +++ b/formal/L4-DYADIC.adoc @@ -0,0 +1,171 @@ +== Ephapax L4 — Dyadic mode (labelling discipline) + +____ +Scope: project / module-level labelling. *No proof obligations.* +Companion mechanical scaffold: link:L4.v[`+formal/L4.v+`]. Canonical +design source: +link:PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md §7+`]. +____ + +=== What L4 is + +L4 is the outermost ring of the four-layer redesign. Where L1 / L2 / L3 +each carry a typing-layer judgment or per-layer proof obligation, L4 +carries *none of that*. It is a _labelling discipline_ applied at the +program / module boundary. + +A closed Ephapax program declares one of three program modes: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|`+ProgramMode+` (L4) |Selects L2 modality |L3 echo discipline that +follows +|`+EphapaxLinear+` |`+Linear+` |Echoes must be *observed* before program +end + +|`+EphapaxAffine+` |`+Affine+` |Echoes may be observed or silently +lowered + +|`+ModuleBoundaryMix+` |per-module |Module’s L2 modality determines its +L3 echo discipline at each boundary crossing +|=== + +The declaration tells the type checker which L2 modality `+m+` to thread +through `+has_type_l1+`. Once `+m+` is selected, L3’s echo observation +discipline follows from L2 with no further user input. + +=== Why L4 is not a separate proof layer + +The four-layer redesign assigns proof obligations as follows: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Layer |What it proves |Where the proof lives +|*L1* |Region-capability soundness; preservation under R-threading +|`+formal/TypingL1.v+` + `+formal/Semantics_L1.v+` + +|*L2* |Modality lifting (Linear ⇒ Affine) |`+formal/Modality.v+` +(`+linear_to_affine+` Qed) + +|*L3* |Echo residue preservation under irreversible steps +|`+formal/Echo.v+` + `+preservation_l3+` in `+formal/Semantics_L1.v+` + +|*L4* |(nothing — labelling only) |`+formal/L4.v+` (Definitions only, no +theorems) +|=== + +L4 is the _user-facing surface_ through which L1–L3’s proofs are applied +to a concrete program. Selecting `+EphapaxLinear+` at L4 instructs the +system to: + +[arabic] +. Choose `+Linear : Modality+` at L2. +. Enforce echo observation at L3. +. Apply L1’s preservation theorem to the resulting derivation. + +No new theorem is needed at L4 because no new judgment is introduced. + +=== The mother–child dyad + +The dyadic framing (PRESERVATION-DESIGN.md §7) reads the Linear / Affine +pair as an *asymmetric dyad*: + +* *Linear* = the obligation-bearer (the _speaker_ of the dyad). +Linear-mode programs are responsible for discharging every echo before +termination. +* *Affine* = the obligation-relaxer (the _listener_). Affine-mode +programs may accept, observe, or silently drop echoes — the listener has +the option of not engaging. + +Both sides are needed because real systems contain both +obligation-bearing components (resource owners, audit trails, GDPR +deletion receipts) and obligation-relaxing components (loggers, UI, +diagnostics that may safely vanish). + +The dyad *is not symmetric*: a Linear-mode component can call into an +Affine-mode component (the listener silently relaxes), but an +Affine-mode component cannot call into a Linear-mode component except +through a designated obligation-handoff (a `+ModuleBoundaryMix+` +declaration explicitly converts the obligation surface). + +This asymmetry is exactly what L2’s `+linear_to_affine+` lemma +mechanises: every Linear derivation can be viewed as an Affine +derivation, but not vice versa. + +=== Orthogonality to L1 / L2 / L3 + +L4 is orthogonal to the lower layers in the following sense: + +* *L1 (region capabilities)* is the _same_ in both Linear and Affine — +both must track region exit precisely for soundness. Switching L4 +program mode does not change L1’s invariants. +* *L2 (modality)* gives the _direction_ of the dyad — which side is +obligation-bearing. L4 selects which L2 modality the program inhabits. +* *L3 (echo)* is the _protocol_ of obligation discharge — what the +observer must do with the residue. L4 selects the L3 discipline +indirectly through L2. + +=== What L4 declarations look like (illustrative) + +L4 is currently a _design layer_. Surface syntax has not yet been wired +into the parser or borrow checker. The intended shape: + +[source,ephapax] +---- +// Top of a closed program, before any imports +#![ephapax_linear] + +// — or — + +#![ephapax_affine] + +// — or, at the boundary of a multi-module project — + +#![module_boundary_mix(default = "ephapax_affine")] +mod critical_section { + #![ephapax_linear] + // every echo here must be observed before this module returns +} +---- + +The mechanical surface for downstream tools is in +link:L4.v[`+formal/L4.v+`]: + +* `+ProgramMode+` — three-constructor enum (`+PModeLinear+` / +`+PModeAffine+` / `+PModeBoundaryMix+`). +* `+program_mode_to_modality+` — total function +`+ProgramMode -> Modality+` (with `+PModeBoundaryMix+` mapped to a +default; per-module modality is read from the surrounding declaration). +* No theorems; no admit; no axiom. + +=== What L4 does NOT do + +L4 introduces *zero* new proof obligations. In particular: + +* L4 does NOT introduce a new typing judgment. +* L4 does NOT introduce a new step relation. +* L4 does NOT introduce a new preservation theorem. +* L4 does NOT introduce a new echo discipline. +* L4 does NOT change the L1 region-threading invariants. +* L4 does NOT change the L2 modality-lifting lemma. + +If a question about L4 leads to a proof obligation, the question is not +about L4 — it is about whichever lower layer the obligation lives in +(probably L2 if it concerns modality lifting, or L3 if it concerns echo +discipline). + +=== Cross-references + +* link:PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md §7+`] — +canonical design source. +* link:PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md §8+`] — +per-layer proof-need separation between Linear and Affine. +* link:L4.v[`+formal/L4.v+`] — the mechanical scaffold (Definitions +only). +* link:Modality.v[`+formal/Modality.v+`] — L2 thin poset; selected by +L4. +* link:Echo.v[`+formal/Echo.v+`] — L3 echo calculus; discipline selected +by L2. +* link:../CLAUDE.md[`+CLAUDE.md+`] — agent guidance; owner directive +2026-05-27 (no patching of legacy preservation). +* link:../PROOF-NEEDS.md[`+PROOF-NEEDS.md+`] — proof-debt accounting. diff --git a/formal/L4-DYADIC.md b/formal/L4-DYADIC.md deleted file mode 100644 index 7fcc0d9f..00000000 --- a/formal/L4-DYADIC.md +++ /dev/null @@ -1,151 +0,0 @@ - - - - -# Ephapax L4 — Dyadic mode (labelling discipline) - -> Scope: project / module-level labelling. **No proof obligations.** -> Companion mechanical scaffold: [`formal/L4.v`](L4.v). -> Canonical design source: [`formal/PRESERVATION-DESIGN.md §7`](PRESERVATION-DESIGN.md). - -## What L4 is - -L4 is the outermost ring of the four-layer redesign. Where L1 / L2 / L3 -each carry a typing-layer judgment or per-layer proof obligation, L4 -carries **none of that**. It is a *labelling discipline* applied at the -program / module boundary. - -A closed Ephapax program declares one of three program modes: - -| `ProgramMode` (L4) | Selects L2 modality | L3 echo discipline that follows | -|---|---|---| -| `EphapaxLinear` | `Linear` | Echoes must be **observed** before program end | -| `EphapaxAffine` | `Affine` | Echoes may be observed or silently lowered | -| `ModuleBoundaryMix` | per-module | Module's L2 modality determines its L3 echo discipline at each boundary crossing | - -The declaration tells the type checker which L2 modality `m` to thread -through `has_type_l1`. Once `m` is selected, L3's echo observation -discipline follows from L2 with no further user input. - -## Why L4 is not a separate proof layer - -The four-layer redesign assigns proof obligations as follows: - -| Layer | What it proves | Where the proof lives | -|---|---|---| -| **L1** | Region-capability soundness; preservation under R-threading | `formal/TypingL1.v` + `formal/Semantics_L1.v` | -| **L2** | Modality lifting (Linear ⇒ Affine) | `formal/Modality.v` (`linear_to_affine` Qed) | -| **L3** | Echo residue preservation under irreversible steps | `formal/Echo.v` + `preservation_l3` in `formal/Semantics_L1.v` | -| **L4** | (nothing — labelling only) | `formal/L4.v` (Definitions only, no theorems) | - -L4 is the *user-facing surface* through which L1–L3's proofs are -applied to a concrete program. Selecting `EphapaxLinear` at L4 instructs -the system to: - -1. Choose `Linear : Modality` at L2. -2. Enforce echo observation at L3. -3. Apply L1's preservation theorem to the resulting derivation. - -No new theorem is needed at L4 because no new judgment is introduced. - -## The mother–child dyad - -The dyadic framing (PRESERVATION-DESIGN.md §7) reads the Linear / -Affine pair as an **asymmetric dyad**: - -- **Linear** = the obligation-bearer (the *speaker* of the dyad). - Linear-mode programs are responsible for discharging every echo - before termination. -- **Affine** = the obligation-relaxer (the *listener*). - Affine-mode programs may accept, observe, or silently drop echoes — - the listener has the option of not engaging. - -Both sides are needed because real systems contain both -obligation-bearing components (resource owners, audit trails, GDPR -deletion receipts) and obligation-relaxing components (loggers, UI, -diagnostics that may safely vanish). - -The dyad **is not symmetric**: a Linear-mode component can call into -an Affine-mode component (the listener silently relaxes), but an -Affine-mode component cannot call into a Linear-mode component except -through a designated obligation-handoff (a `ModuleBoundaryMix` -declaration explicitly converts the obligation surface). - -This asymmetry is exactly what L2's `linear_to_affine` lemma -mechanises: every Linear derivation can be viewed as an Affine -derivation, but not vice versa. - -## Orthogonality to L1 / L2 / L3 - -L4 is orthogonal to the lower layers in the following sense: - -- **L1 (region capabilities)** is the *same* in both Linear and - Affine — both must track region exit precisely for soundness. - Switching L4 program mode does not change L1's invariants. -- **L2 (modality)** gives the *direction* of the dyad — which side - is obligation-bearing. L4 selects which L2 modality the program - inhabits. -- **L3 (echo)** is the *protocol* of obligation discharge — what - the observer must do with the residue. L4 selects the L3 - discipline indirectly through L2. - -## What L4 declarations look like (illustrative) - -L4 is currently a *design layer*. Surface syntax has not yet been -wired into the parser or borrow checker. The intended shape: - -```ephapax -// Top of a closed program, before any imports -#![ephapax_linear] - -// — or — - -#![ephapax_affine] - -// — or, at the boundary of a multi-module project — - -#![module_boundary_mix(default = "ephapax_affine")] -mod critical_section { - #![ephapax_linear] - // every echo here must be observed before this module returns -} -``` - -The mechanical surface for downstream tools is in [`formal/L4.v`](L4.v): - -- `ProgramMode` — three-constructor enum (`PModeLinear` / - `PModeAffine` / `PModeBoundaryMix`). -- `program_mode_to_modality` — total function `ProgramMode -> Modality` - (with `PModeBoundaryMix` mapped to a default; per-module modality is - read from the surrounding declaration). -- No theorems; no admit; no axiom. - -## What L4 does NOT do - -L4 introduces **zero** new proof obligations. In particular: - -- L4 does NOT introduce a new typing judgment. -- L4 does NOT introduce a new step relation. -- L4 does NOT introduce a new preservation theorem. -- L4 does NOT introduce a new echo discipline. -- L4 does NOT change the L1 region-threading invariants. -- L4 does NOT change the L2 modality-lifting lemma. - -If a question about L4 leads to a proof obligation, the question is -not about L4 — it is about whichever lower layer the obligation lives -in (probably L2 if it concerns modality lifting, or L3 if it concerns -echo discipline). - -## Cross-references - -- [`formal/PRESERVATION-DESIGN.md §7`](PRESERVATION-DESIGN.md) — - canonical design source. -- [`formal/PRESERVATION-DESIGN.md §8`](PRESERVATION-DESIGN.md) — - per-layer proof-need separation between Linear and Affine. -- [`formal/L4.v`](L4.v) — the mechanical scaffold (Definitions only). -- [`formal/Modality.v`](Modality.v) — L2 thin poset; selected by L4. -- [`formal/Echo.v`](Echo.v) — L3 echo calculus; discipline selected - by L2. -- [`CLAUDE.md`](../CLAUDE.md) — agent guidance; owner directive - 2026-05-27 (no patching of legacy preservation). -- [`PROOF-NEEDS.md`](../PROOF-NEEDS.md) — proof-debt accounting. diff --git a/formal/PHASE-D-REDESIGN.adoc b/formal/PHASE-D-REDESIGN.adoc new file mode 100644 index 00000000..d6246be5 --- /dev/null +++ b/formal/PHASE-D-REDESIGN.adoc @@ -0,0 +1,792 @@ +== Phase D — L2 effect-typed TFun, redesign memo (2026-05-28) + +This memo refines the Phase D plan in +link:PRESERVATION-DESIGN.md[`+PRESERVATION-DESIGN.md §5.1+`] based on +analytical findings from the 2026-05-28 implementation attempt. *Read +this before continuing Phase D work; it supersedes earlier +slice-by-slice framings that omit the substitution-lemma side condition +introduced below.* + +=== What landed + +Two PRs landed on `+main+` during the 2026-05-28 session as part of +Phase D’s scaffolding plus an independent reformulation: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|PR |Title |What +|#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. + +|#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.* +|=== + +=== What we tried for slice 2 — and what broke + +The 2026-05-28 slice 2 attempt added two new typing rules to +`+TypingL1.v+`: + +[source,coq] +---- +| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, + R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> + R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G + +| T_Lam_L1_Affine_Eff : forall R G T1 T2 e u R_in R_out, + R_in ; ctx_extend G T1 |=L1[Affine] e : T2 -| R_out ; (T1, u) :: G -> + R ; G |=L1[Affine] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G +---- + +Adding new constructors to `+has_type_l1+` forces every existing +inductive lemma over `+has_type_l1+` to cover the new cases. The +following lemmas were exercised: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Lemma |T_Lam_L1_*_Eff case |Outcome +|`+count_occ_le_l1_m+` |R-preserving outer |(not hit in this attempt; +would close trivially) + +|`+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.+` + +|`+shift_typing_gen_l1_m+` |Body’s context extends by T1; shift by (S k) +|✅ closed via same pattern as legacy T_Lam + +|`+subst_typing_gen_l1_m+` |*Substituted value lives at outer R, body at +R_in* |🛑 *STRUCTURAL FAIL* +|=== + +The substitution lemma’s `+T_Lam_L1_Linear_Eff+` case decomposes as: + +* Hypothesis: `+Hregv : In rv R+` (substituted value’s region is in the +outer environment). +* Goal: produce a body derivation at `+R_in+` with `+rv+` substituted +in. The body’s typing rules (T_Loc_L1, etc.) for occurrences of the +substituted variable require `+In rv R_in+`. +* Available: nothing that ties `+R+` to `+R_in+`. + +*`+In rv R+` does not imply `+In rv R_in+`* under the current rule +shape. The body’s `+R_in+` is a free parameter of the lambda type — the +lambda value can declare any `+R_in+` it likes. There is no enforced +connection between `+R_in+` and the regions of the bound variable’s type +at lambda formation. Substitution lemma is therefore not generalisable +to the new constructors without an additional invariant. + +=== The structural insight + +This is the _same_ missing invariant we hit at: + +* `+Semantics_L1.v:553/621+` (Phase C — shadowed `+T_Region_Active_L1+`) +* `+Semantics_L1.v:1694+` (`+preservation_l1+` lambda-rigidity) +* Sub-sub-case (i) of Phase C’s option (c) attempt (`+feedback+`: +body-input-shrinkage is not a theorem in general) + +Each instance is the L1 vocabulary lacking a way to relate the *outer* +region environment (where caller values live) to a *nested* region +environment (where a lambda body, or a region’s scoped body, operates). +The L1 rule shapes treat outer and inner environments as independent, +but *substitution and call sites require a connection*. + +=== The redesign — side condition on `+T_Lam_L1_*_Eff+` + +Add an explicit side condition to `+T_Lam_L1_*_Eff+` requiring the bound +variable’s regions to be live in the body’s input environment: + +[source,coq] +---- +| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, + (forall r, In r (Typing.free_regions T1) -> In r R_in) -> (* NEW *) + R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> + R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G + +| T_Lam_L1_Affine_Eff : forall R G T1 T2 e u R_in R_out, + (forall r, In r (Typing.free_regions T1) -> In r R_in) -> (* NEW *) + R_in ; ctx_extend G T1 |=L1[Affine] e : T2 -| R_out ; (T1, u) :: G -> + R ; G |=L1[Affine] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G +---- + +*Interpretation*: any region appearing in the bound variable’s type +`+T1+` is guaranteed to be live in the body’s input environment +`+R_in+`. When substituting a value `+v : T1+` with +`+free_regions T1 ⊇ {rv}+` into the body, `+In rv R_in+` follows from +the side condition. + +For typical lambda types: + +* `+T1 = TBase TUnit+`: `+free_regions T1 = []+`. Side condition +vacuous. No constraint on R_in. ✓ +* `+T1 = TString rv+`: `+free_regions T1 = [rv]+`. Side condition forces +`+In rv R_in+`. ✓ +* `+T1 = TPair (TString r1) (TString r2)+`: forces both `+r1+` and +`+r2+` in R_in. ✓ +* `+T1 = TFun T1' T2'+`: forces all free regions of T1’ and T2’ into +R_in. ✓ (Closures over substring-typed values carry their region +requirements.) + +This side condition is *discharge-able at lambda formation time*: the +caller forming the lambda must establish the constraint. In practice +this is automatic — any program that types a lambda body referencing a +region in T1 already has that region in scope. + +=== Why this closes `+subst_typing_gen_l1_m+` + +In the T_Lam_L1_Linear_Eff case of the substitution lemma: + +* Hypothesis from rule: +`+H : forall r, In r (free_regions T1) -> In r R_in+`. +* The substituted value `+vv+` has `+In rv R+` (lemma’s preexisting +Hregv) AND `+vv : T1+` with `+In rv (free_regions T1)+` (since +`+linear_value_is_loc_l1+` constrains rv to be T1’s region). +* Apply H: `+In rv R_in+`. ✓ +* Body typing after substitution uses `+In rv R_in+` to discharge +T_Loc_L1’s `+In r R+` premise at the substituted variable’s occurrences. + +The lemma case closes via the same pattern as legacy T_Lam_L1_Linear but +threaded through R_in instead of outer R. + +=== Slice plan (revised) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Slice |Scope |Status +|1 |TFunEff syntax (`+Syntax.v+` + `+free_regions+`) |✅ MERGED PR #200 + +|2 (redesigned) |`+T_Lam_L1_*_Eff+` with side condition `+R ⊆ R_in+`; +cascade through 3 inductive lemmas in `+Semantics_L1.v+` |✅ MERGED PR +#204 + +|3 — first attempt |`+T_App_L1_Eff+` with explicit count-le premise |⚠️ +*REVERTED 2026-05-28* — see addendum below + +|3 — redesign |TBD — see addendum below |Pending + +|4 |`+preservation_l1+` lambda-rigidity closure +(`+Semantics_L1.v:1694+`) |Pending + +|5 |Phase B Slice 1 (TEcho linearity wire) + Phase C (list-vs-multiset +bridge) unlocks |Pending +|=== + +=== Slice 3 addendum — type-embedded R blocks env-shrinkage (2026-05-28) + +The first slice 3 attempt added the rule + +[source,coq] +---- +| T_App_L1_Eff : forall m R R_pre G G' G'' e1 e2 T1 T2 R_in R_out, + R ; G |=L1[m] e1 : TFunEff T1 T2 R_in R_out -| R_pre ; G' -> + R_pre ; G' |=L1[m] e2 : T1 -| R_in ; G'' -> + (forall r, count_occ string_dec R_out r <= count_occ string_dec R_in r) -> + R ; G |=L1[m] EApp e1 e2 : T2 -| R_out ; G'' +---- + +`+count_occ_le_l1_m+`’s case closed via the count-le premise + the IHs. +`+region_shrink_preserves_typing_l1_gen_m+`’s case did NOT close, +exposing the deeper structural issue. + +*The issue:* TFunEff embeds R_in / R_out in the type T. When the +env-shrinkage lemma removes one occurrence of `+rr+` from the outer R, +the rule’s structure forces: + +* e1’s TFunEff type is preserved (lemma signature: `+T+` unchanged). +* e2’s IH gives output `+remove_first rr R_in+` (shrunk version of e2’s +original output). +* The rule’s `+R_in+` premise is `+R_in+` (from e1’s TFunEff, +unchanged). + +These mismatch unless `+rr ∉ R_in+`. Similarly the lemma’s expected +output `+remove_first rr R_out+` vs the rule’s output `+R_out+` requires +`+rr ∉ R_out+`. The natural precondition `+~ In rr (free_regions T)+` +would give both +(`+free_regions (TFunEff T1 T2 R_in R_out) ⊇ R_in ∪ R_out+`). + +*Adding the precondition cascades:* the lemma’s induction would need +every sub-IH to also satisfy a type-precondition for the +sub-expression’s type. For `+T_App_L1+`, e2’s type is `+T1+` (the +argument type), which is NOT constrained by the parent’s HnotT (parent +only constrains `+T2+`, the return type). So `+T_App_L1+`’s case BREAKS +under the strengthened precondition. + +Strengthening further (e.g., "`no intermediate type contains `+rr+``") +is not a clean Prop and would need a meta-condition over derivations. + +*Decision:* REVERT slice 3’s `+T_App_L1_Eff+`. The rule is too tightly +coupled to env-shrinkage with the current +`+region_shrink_preserves_typing_l1_gen_m+` signature. + +*Future redesign options for slice 3:* + +[arabic] +. *Lift env-shrinkage out of the lemma’s general statement.* Split +`+region_shrink_preserves_typing_l1_gen_m+` into (i) a value-restricted +shape that handles TFunEff vacuously (no T_App in value derivations), +and (ii) a non-value shape with TFunEff _explicitly forbidden_ via a +type-shape predicate. Application sites of `+_gen_m+` choose which +version they need. +. *Make `+T_App_L1_Eff+`’s premise pass through an explicit +"`env-frame`" rather than the function’s R_in/R_out.* I.e., the rule +says "`given e1 typing at TFunEff with effect (R_in, R_out), and a frame +Δ such that the call-site env is `+Δ ∪ R_in+`, the post-call env is +`+Δ ∪ R_out+`.`" This decouples the embedded R from the call-site env. +Bigger redesign. +. *Defer `+T_App_L1_Eff+` indefinitely.* Phase D’s main payoff (closing +lambda-rigidity at `+Semantics_L1.v:1694+`) doesn’t strictly require +`+T_App_L1_Eff+` if we can argue preservation for β-reduction on +`+T_Lam_L1_*_Eff+`-typed lambdas via a different path. Investigation +owed. + +The 2026-05-28 attempt’s diff is preserved in git history (commit on the +now-deleted branch); see `+git log --all+` for archaeology. + +=== Slice 3 sub-addendum — option 4 (meta-lemma) analysis (2026-05-28, session 4) + +The 2026-05-28 session 3 close-out flagged a fourth option not in the +list above and tagged it "`UNTRIED — most promising`": + +____ +*Option 4 — Meta-lemma approach.* Strengthen +`+expr_strictly_free_of_region+`’s `+ELam+` case to recurse into the +type annotation: + +[source,coq] +---- +| ELam T body => ~ In r (Typing.free_regions T) /\ expr_strictly_free_of_region r body +---- + +Add a context-freedom precondition +`+forall i T u, ctx_lookup G i = Some (T, u) -> ~ In r (free_regions T)+` +to `+region_shrink_preserves_typing_l1_gen_m+`. Then prove a meta-lemma +`+strict_free_implies_type_free+` that derives +`+~ In rr (free_regions T)+` from `+expr_strictly_free_of_region rr e+` ++ the typing derivation + context-freedom. The shrinkage lemma’s +`+T_App_L1_Eff+` case closes by applying the meta-lemma to e1’s TFunEff +type, extracting `+~ In rr R_in+` and `+~ In rr R_out+`, then +mechanically matching the rule’s premises with the env-shrunk IHs. +____ + +Session 4 (this addendum) analysed option 4 in detail and *found it +provably non-viable*. Recording the analysis here so that future +sessions don’t re-attempt it. + +==== Why the meta-lemma fails at `+T_Lam_L1_*_Eff+` + +The proposed meta-lemma: + +[source,coq] +---- +strict_free_implies_type_free : + forall rr e R G T R' G' m, + ctx_free_of rr G -> (* context-freedom precondition *) + expr_strictly_free_of_region rr e -> + R ; G |=L1[m] e : T -| R' ; G' -> + ~ In rr (free_regions T) +---- + +would induct on the typing derivation. The `+T_Lam_L1_Linear_Eff+` case +(the rule that landed in slice 2, `+TypingL1.v:221-224+`) has the shape: + +[source,coq] +---- +| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, + (forall r, In r R -> In r R_in) -> + R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> + R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G +---- + +In the meta-lemma’s induction at this case: + +* Expression: `+ELam T1 body+`. +* Strengthened strict-free of `+ELam T1 body+` decomposes (per the +proposed strengthening) to +`+~ In rr (free_regions T1) /\ expr_strictly_free_of_region rr body+`. +* Conclusion type: `+TFunEff T1 T2 R_in R_out+`. Its `+free_regions+` +(per `+Typing.v:55-71+`, including the slice 1 case +`+TFunEff T1 T2 R_in R_out => free_regions T1 ++ free_regions T2 ++ R_in ++ R_out+`) +is the four-way concatenation. +* The goal `+~ In rr (free_regions (TFunEff T1 T2 R_in R_out))+` +decomposes into four conjuncts: +** `+~ In rr (free_regions T1)+` ← available from the strengthened +strict-free hypothesis. ✓ +** `+~ In rr (free_regions T2)+` ← available from the IH on `+body+` +(typed at `+T2+` in the extended context, with the strengthened +strict-free of `+body+`, and context-freedom of the extended +`+ctx_extend G T1+` derivable from outer context-freedom + the first +conjunct). ✓ +** `+~ In rr R_in+` ← *NO SOURCE.* +** `+~ In rr R_out+` ← *NO SOURCE.* + +`+R_in+` and `+R_out+` are *free parameters of the rule*. They appear in +the conclusion type but are not constrained by anything in the lambda’s +syntactic form. The rule’s side condition +`+forall r, In r R -> In r R_in+` is unidirectional (R ⊆ R_in), so it +gives `+In r R → In r R_in+`, not `+~ In r R_in → ~ In r R+`. The body’s +typing premise (`+R_in ; ... |- body : T2 -| R_out+`) likewise +constrains R_in/R_out only by what the body needs, not by what `+rr+` +doesn’t touch. + +In short: *the strengthened strict-free predicate covers syntactic +occurrences of `+rr+` in `+ELam T1 body+` (which is just T1 and body), +but cannot reach `+R_in+` and `+R_out+` because those live only in the +_typing derivation_, not in the _expression_.* + +==== Why this is the same impedance mismatch as the original slice 3 blocker + +The original slice 3 blocker (session 3, this document above) was: +`+region_shrink_preserves_typing_l1_gen_m+`’s `+T_App_L1_Eff+` case +couldn’t close because the IH on `+e2+` gives `+remove_first rr R_in+` +while the rule’s premise demands `+R_in+`. + +The proposed fix via option 4 (meta-lemma) *relocates the same mismatch* +to a different proof obligation: the meta-lemma’s `+T_Lam_L1_*_Eff+` +case can’t close because `+R_in+`/`+R_out+` are syntax-invisible free +parameters. + +The root cause is the same in both presentations: *the L1 vocabulary of +`+expr_strictly_free_of_region+` cannot constrain the effect-typing +annotations carried in `+TFunEff+`*, because effect annotations are a +_typing_ artifact, not a _syntactic_ one. + +==== Adjacent options also fail for the same reason + +Two repair attempts on option 4 also fail: + +[arabic] +. *Require `+R_in ⊆ free_regions T1 ∪ free_regions T2+`* as an extra +side condition on `+T_Lam_L1_*_Eff+`. This would let `+~ In rr T1+` + +`+~ In rr T2+` (from strict-free + IH) imply `+~ In rr R_in+`. +*Problem*: this over-constrains R_in. A lambda body that uses regions +_not_ in its parameter or return type (e.g., a lambda that allocates a +transient region for its body’s local work, drops it before returning) +would no longer type — its R_in legitimately exceeds the type’s free +regions. +. *Require `+R_in+` to be minimal* (exactly the regions the body +actually uses, derivable as a function of body syntax). *Problem*: this +requires a "`region inference`" function that is decidable and +structurally faithful. The current ephapax grammar doesn’t expose enough +structure at lambda formation to compute this; we’d need annotations or +a fresh inference pass. + +Both repairs require slice 2’s already-landed rule shape to change. Even +granting the change, they trade off compositionality (call sites that +wrap lambdas with extra-region preludes/postludes break under a +minimal-R_in regime). + +==== Implication for the slice 3 redesign + +Option 4 is removed from the candidate list. The remaining candidates +are: + +[arabic] +. *Split lemma* (option 1) — value-restricted vs non-value variants of +`+region_shrink_preserves_typing_l1_gen_m+`. *Caveat surfaced in session +4*: the value-restricted variant’s `+T_Lam_L1_*+` cases still recurse +into non-value bodies via IH, so the non-value variant must exist and +must handle `+T_App_L1_Eff+`. The split doesn’t make `+T_App_L1_Eff+`’s +case go away; it just moves it. +. *Env-frame Δ* (option 2) — restructure `+T_App_L1_Eff+`’s premise so +the call-site env is `+Δ ++ R_in+` and the post-call env is +`+Δ ++ R_out+`. *Caveat surfaced in session 4*: `+remove_first+` on +`+Δ ++ R_in+` still removes from R_in if `+rr ∈ R_in+`. The frame Δ +doesn’t shield R_in from env shrinkage unless we further require +`+rr ∉ R_in+`. The deeper fix is still needed. +. *Defer `+T_App_L1_Eff+` indefinitely* (option 3) — explicitly accept +that TFunEff lambdas can be formed but never called via `+has_type_l1+`. +*Status from session 3*: rejected because slice 4 (preservation_l1 +lambda-rigidity closure) requires β-reduction typing for TFunEff +lambdas. *Status from session 4*: re-examined and the rejection still +holds, but with one nuance — preservation_l1’s `+S_App_Step2+` case can +be made vacuous for TFunEff function values _if_ we can prove "`an +`+EApp v1 e2+` with `+v1 : TFunEff T1 T2 R_in R_out+` does not type via +`+has_type_l1+``" as a meta-fact. This is true (no T_App rule produces a +TFunEff conclusion at the function position), so option 3 is _partially_ +viable for slice 4: it closes lambda-rigidity for _uncallable_ TFunEff +lambdas. Slice 5’s broader effect-typing work would still need +T_App_L1_Eff or a successor. + +==== Option 5 — level-split + +A new option surfaces from the session 4 analysis. The structural +insight is that *T_App_L1_Eff is doing L2 work* (calling an effect-typed +function is fundamentally a modality/effect operation, not a region +operation), but it currently lives in L1 (`+has_type_l1+`). Move it to +L2: + +* `+has_type_l1+` (L1) — knows `+TFunEff+` exists as a type former +(slice 1, already landed) and can type lambdas at it (slice 2, already +landed), but *does not provide an application rule for TFunEff*. EApp on +a TFunEff function does not type at L1. +* `+has_type_l2+` (L2) — wraps `+has_type_l1+` with a `+T_App_L2_Eff+` +rule that handles application of TFunEff lambdas. The L2 judgment +becomes the canonical home for effect-typed application. + +Properties: + +* `+region_shrink_preserves_typing_l1_gen_m+` only needs to handle the +L1 judgment, where T_App_L1_Eff doesn’t exist. Slice 3’s blocker +disappears at the L1 level. +* `+preservation_l1+` lambda-rigidity closure (slice 4): TFunEff lambdas +are still uncallable at L1, but L1 was never the right venue for their +β-reduction. Lambda-rigidity at the L1 level closes for _legacy_ lambdas +via existing means; TFunEff lambdas are inert at L1, and inert values +preserve trivially. +* `+preservation_l2+`: a new theorem (slice 5 work) handles β-reduction +for TFunEff lambdas under the L2 judgment. The L2 vocabulary can +introduce effect-aware env-shrinkage lemmas designed for TFunEff from +the start, without retrofitting an L1 lemma. + +Trade-off: option 5 splits T_Lam_L1_*_Eff (L1) from T_App_L2_Eff (L2). +The introduction and elimination of TFunEff live at different layers. +This is unusual for type formers but defensible because the introduction +is "`syntactically lambda-shaped`" (an L1 syntactic form with extra +type-level annotations) while elimination is "`effect-semantic`" +(consuming the effect annotations meaningfully). The current +`+TypingL2.v+` is described as a "`thin wrapper through +`+TypingL1.has_type_l1+``"; option 5 thickens it with at least the +T_App_L2_Eff rule. + +==== Recommendation for owner decision + +Session 4 cannot pick among options 1, 2, 3, and 5 without owner input — +the choice is a layer-design decision, not a proof-tactics decision. +Each option has a different downstream cost profile: + +[width="100%",cols="25%,25%,25%,25%",options="header",] +|=== +|Option |Slice 3 cost |Slice 4 closure |Slice 5 unblock +|1 (split lemma) |High (rewrite `+_gen_m+`; non-value variant still +blocked) |Partial |Unclear + +|2 (env-frame Δ) |Medium-high (`+T_App_L1_Eff+` redesign + cascade) +|Should close |Unclear; might cascade-break Phase B Slice 1 + +|3 (defer) |Low (no code change) |Closes for legacy lambdas only +|Blocked until 5 lands + +|5 (level-split) |Medium (new T_App_L2_Eff in `+TypingL2.v+`; thicken L2 +wrapper) |Closes at L1 (TFunEff inert at L1) |Natural L2 venue for full +effect typing +|=== + +Per the CLAUDE.md owner directive "`DO escalate before patching`": this +addendum *escalates the slice 3 redesign decision to the owner*. No code +lands until the option is selected. + +==== What does NOT change + +* PR #200 (TFunEff syntax) stays. +* PR #201 (strict predicate, blocker 5 closure) stays. +* PR #203 (this design memo) stays. +* PR #204 (T_Lam_L1_*_Eff rules + R ⊆ R_in side condition cascade) +stays. +* PR #205 (slice 3 first attempt findings) stays. +* Counterexample.v (legacy preservation is false) is untouched in all +four options. + +*What slice 3 was supposed to unlock:* β-reduction for +`+T_Lam_L1_*_Eff+`-typed lambdas (preservation_l1 case S_App_Fun) would +use `+T_App_L1_Eff+`’s typing. Without `+T_App_L1_Eff+`, TFunEff lambdas +can be _formed_ but never _called_ via has_type_l1 — slice 2’s +contribution is preserved but standalone (no programs exercise it yet). + +The strict-predicate reformulation (PR #201) is independent of these +slices and already merged. + +=== Implementation notes for slice 2 + +[arabic] +. The 2 rules: add right after `+T_Lam_L1_Affine+` at `+TypingL1.v:183+` +(verified compile order works). +. The side condition is a +`+forall r, In r (free_regions T1) -> In r R_in+` Prop — straightforward +to discharge in induction cases. +. *Cascade to 8 inductive Qed lemmas* in `+Semantics_L1.v+`: +* `+count_occ_le_l1_m+` — trivial (R-preserving outer) +* `+region_shrink_preserves_typing_l1_gen_m+` — re-apply rule with body +unchanged + side condition unchanged (region shrink at outer doesn’t +touch the body’s premise) +* `+typing_preserves_length_l1+` — trivial +* `+typing_preserves_bindings_l1+` — mirrors legacy T_Lam case +* `+unrestricted_flag_unchanged_l1+` — similar +* `+shift_typing_gen_l1_m+` — mirrors legacy T_Lam case +* `+value_R_G_preserving_l1+` — lambda is a value; R-preserving case +trivial +* `+subst_typing_gen_l1_m+` — *uses the side condition* to discharge +`+In rv R_in+` from `+In rv (free_regions T1)+` +. `+Counterexample.v+` is unaffected (no TFunEff or T_Lam_L1_*_Eff +usage). + +=== Owner directive compliance + +Per CLAUDE.md owner directive 2026-05-27: + +* ✅ Zero new `+Admitted.+` or `+Axiom.+` declarations +* ✅ No patching of `+Semantics.v+` `+preservation+` (provably false) +* ✅ No patching of legacy `+Typing.v+` judgment +* ✅ Counterexample.v regression theorem untouched +* ✅ All commits GPG-signed +* ✅ Auto-merge ON for every PR + +=== Anti-patterns this redesign avoids + +* *Strengthening `+subst_typing_gen_l1_m+`’s preconditions* with a +per-call-site `+In rv R_in+` hypothesis would cascade to every caller +and force them to prove the connection ad-hoc. The side condition at +rule formation localises the constraint. +* *Separate `+has_type_l1_eff+` judgment* would double the lemma surface +and require bridge lemmas. Avoided by extending `+has_type_l1+` in place +with a clean side condition. +* *Constraining R_in = outer R* would defeat the purpose of +effect-typing. Avoided. + +=== Cross-references + +* link:PRESERVATION-DESIGN.md[`+PRESERVATION-DESIGN.md §5.1+`] — +original Phase D outline. +* link:../PROOF-NEEDS.md[`+PROOF-NEEDS.md+`] — proof debt audit (Phase B +** Phase C deferral rows reference this Phase D closure). +* `+Semantics_L1.v:553/621+` — Phase C structural admits that close once +slice 4+5 land. +* `+Semantics_L1.v:1694+` — `+preservation_l1+` lambda-rigidity admit +that closes at slice 4. + +=== Future re-attempts of perm_l1 (agent finding 2026-05-28) + +The standalone `+region_env_perm_typing_l1+` (existential output) agent +attempt 2026-05-28 found 7 admits across two failure families: + +[arabic] +. R-stability rules (`+T_Lam_L1_*+`, `+T_Borrow_Val_L1+`, `+T_Echo_L1+`) +— force output_R = input_R as lists; IH gives perm-equiv not list-equal. +. Branch-agreement rules (`+T_Case_L1_*+`, `+T_If_L1_*+`) — branches +yield outputs perm-equiv to a join but not list-equal. + +Both families dissolve once the L2 effect-typed structure is in place: +R-stability rules get effect-typed wrappers that don’t force list +equality, and branch-agreement gets a per-branch effect that the join +sums. + +`+perm_l1+` is therefore unblocked at slice 5 (post-T_App_L1_Eff +landing), not at slice 2. + +=== Slice 3 sub-sub-addendum — option 5a picked, slice plan revised (2026-05-28, session 5) + +The owner picked *option 5a* (level-split, intro stays at L1, elim moves +to L2) on 2026-05-28 after the session 4 analysis (slice 3 sub-addendum +above) and a session 5 design-doc cross-check. + +==== Why 5a is the right call — design-doc alignment + +`+PRESERVATION-DESIGN.md+` §5.1 lines 468-474 already endorse the +L1-intro / L2-elim split explicitly: + +____ +*Why this isn’t L1’s job.* Effect-typed function types are a +typing-layer property, not a region-layer property. Adding them to L1’s +unparameterised judgment would conflate the two. *L2 is the natural +home*: the modality parameter is _already_ a typing-layer decoration; +the effect annotation rides alongside it. *After L2’s effect-typed TFun +lands, L1’s gap closes by importation, not by re-deriving L1.* +____ + +The doc consistently names the introduction rule `+T_Lam_L1_Linear+` / +`+T_Lam_L1_Affine+` with the `+_L1_+` infix, confirming that intro lives +in L1. Option 5a is therefore not an architectural departure — it +returns the slice plan to the design-doc-prescribed line, which session +3’s first slice-3 attempt (T_App_L1_Eff in `+has_type_l1+`) had deviated +from. + +§10’s implementation order ("`each step’s correctness is independent of +the next`") imposes no sequencing constraint that blocks 5a. + +==== 5a vs 5b + +Two sub-variants of option 5 exist: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Sub-option |What |Disposition +|*5a (picked)* |Intro stays at L1 via PR #204; elim adds +`+T_App_L2_Eff+` to `+has_type_l2+`. |*Picked.* Smallest delta from +current main. Design-doc-aligned. + +|5b |Retract PR #204’s L1 intro landing; move both intro and elim into +L2. |Discarded — retracts an already-merged PR for marginal +architectural neatness; trades real churn for theoretical symmetry. +|=== + +==== What changes vs the original slice plan + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Slice |Original plan |Option 5a revised plan +|1 |TFunEff syntax |Unchanged. ✅ MERGED PR #200 + +|2 |`+T_Lam_L1_*_Eff+` with R ⊆ R_in side condition |Unchanged — _but +reframed as "`inert introduction at L1`"_. ✅ MERGED PR #204 + +|3 |`+T_App_L1_Eff+` in `+has_type_l1+` |*MOVED*: `+T_App_L2_Eff+` lands +in `+has_type_l2+` (TypingL2.v) as a sibling of `+L2_lift_l1+`. + +|4 |`+preservation_l1+` lambda-rigidity closure |*SPLIT* into 4a +(effect-typed path) + 4b (legacy path) — see below. + +|5 |Broader effect-typing + Phase B/C unblocks |Naturally hosted in L2 +with effect-aware lemmas; `+preservation_l2+` is the new theorem target. +|=== + +==== Slice 4 split — what option 5a does and does not close at L1 + +The session 4 addendum noted that under option 5a, "`TFunEff lambdas are +inert at L1, and inert values preserve trivially.`" A session 5 subagent +verification confirmed this holds for _one of two_ sub-cases inside +`+preservation_l1+`’s `+S_App_Step2+`: + +* *Slice 4a — TFunEff lambdas (effect-typed path).* Under option 5a +there is no `+T_App_L1_Eff+` rule, and the legacy `+T_App_L1+` at +`+TypingL1.v:231-234+` requires `+e1 : TFun T1 T2+`, NOT `+TFunEff+`. No +coercion bridge exists (`+linear_to_affine+` is structural; T_Var/T_Loc +cannot hide TFunEff). Therefore any `+EApp v1 e2+` derivation at L1 with +`+v1+` typed via `+T_Lam_L1_*_Eff+` is vacuous: no L1 derivation reaches +this case. The corresponding sub-case in `+preservation_l1+` closes by +inversion vacuity. *No new proof work needed.* +* *Slice 4b — legacy `+TFun+` lambdas (body-R-rigidity path).* The +legacy `+T_Lam_L1_Linear+` / `+T_Lam_L1_Affine+` rules at +`+TypingL1.v:177-184+` still produce `+TFun T1 T2+`, and `+T_App_L1+` at +`+TypingL1.v:231-234+` still accepts them. The body-R-rigidity gap +documented in `+Semantics_L1.v:1708-1713+` _remains_ for these lambdas +under option 5a — option 5a’s level-split does *not* add structural +information that closes legacy `+TFun+` lambda-body preservation across +an R-shift. *This sub-case stays gated.* + +Implication: `+preservation_l1+` as currently stated (over the whole +`+has_type_l1+` judgment) cannot Qed under option 5a alone. The natural +closure venue is `+preservation_l2+` — a new theorem stated over +`+has_type_l2+`, where effect-typed paths are fully covered and legacy +`+TFun+` paths are honestly carried forward via L1 importation (with the +legacy body-R-rigidity admit recorded as inherited debt rather than +concealed). + +==== The T_App_L2_Eff rule design + +The L2 elimination rule mirrors `+T_App_L1+` but with effect threading +through R_in/R_out: + +[source,coq] +---- +| T_App_L2_Eff : forall m R R1 G G' G'' e1 e2 T1 T2 R_in R_out, + has_type_l2 m R G e1 (TFunEff T1 T2 R_in R_out) R1 G' -> + has_type_l2 m R1 G' e2 T1 R_in G'' -> + has_type_l2 m R G (EApp e1 e2) T2 R_out G'' +---- + +Reading: e1 produces an effect-typed lambda (consuming R → R1, where R1 +is the intermediate env after e1’s evaluation). e2 evaluates the +argument, threading R1 → R_in (the lambda’s input expectation). The +lambda body then runs, consuming R_in and producing R_out. The whole +`+EApp+` expression’s output env is R_out. + +The e1 sub-derivation at type `+TFunEff T1 T2 R_in R_out+` lifts from an +L1 derivation via `+L2_lift_l1+` (since `+T_Lam_L1_*_Eff+` rules are +still the source of TFunEff typings — option 5a keeps intro at L1). + +*Side condition discharge*: PR #204’s +`+(forall r, In r R -> In r R_in)+` constraint (R ⊆ R_in on the lambda’s +input env) was the load-bearing invariant for slice 2’s substitution +closure. At elimination time (T_App_L2_Eff), this constraint flows +naturally — the lambda was formed at R ⊆ R_in, and the call site +supplies R_in directly via e2’s output env. No new side condition is +needed at elimination. + +==== Implementation surface (verified by session 5 subagent audit) + +`+formal/TypingL2.v+` currently has a single constructor `+L2_lift_l1+` +at lines 85-91 — a modality-indexed lift-only inductive ready for +thickening. Adding `+T_App_L2_Eff+` as a sibling constructor (insertion +point: after line 91) is additive and has zero downstream consumers in +`+formal/*.v+` (only the file itself references `+has_type_l2+`). +Existing utilities (`+weaken_modality+`, `+weaken_modality_le+`, +`+lift_l1_to_*+`, `+project_l2_to_l1+`) continue to work — they bridge +via `+L2_lift_l1+` for L1-derived facts and the new constructor adds an +independent path for T_App_L2_Eff derivations. + +==== Slice plan (post-option-5a) + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Slice |Scope |Status +|1 |TFunEff syntax |✅ MERGED PR #200 + +|2 |`+T_Lam_L1_*_Eff+` rules with R ⊆ R_in side condition |✅ MERGED PR +#204 + +|3 (option 5a) |`+T_App_L2_Eff+` in TypingL2.v as a sibling constructor +to `+L2_lift_l1+` |*Next* + +|4a |TFunEff path inert-at-L1 (closes by inversion vacuity) |Bundled +with slice 5 (`+preservation_l2+`) + +|4b |Legacy `+TFun+` body-R-rigidity at preservation_l1 |*Stays gated*; +honest admit carried forward into `+preservation_l2+` via L1 importation + +|5 |`+preservation_l2+` (new theorem); broader effect-typing; Phase B/C +unblocks |After slice 3 +|=== + +==== What does NOT change + +* PR #200 (TFunEff syntax) — unchanged. +* PR #201 (strict predicate, blocker 5 closure) — unchanged. +* PR #203 (Phase D memo) — unchanged. +* PR #204 (T_Lam_L1_*_Eff rules) — unchanged. Reframed as "`inert +introduction at L1.`" +* PR #205 (slice 3 first attempt findings) — unchanged (archaeology). +* PR #207 (slice 3 sub-addendum, option 4 non-viability) — unchanged. +* Counterexample.v — untouched. + +==== Disposition of session 4’s candidate matrix + +[width="100%",cols="50%,50%",options="header",] +|=== +|Option |Disposition +|1 (split lemma) |Discarded — non-value variant still blocked per +session 4 caveat. + +|2 (env-frame Δ) |Discarded — `+remove_first+` still cuts R_in per +session 4 caveat. + +|3 (defer) |Discarded — punts the design question; slice 5 ends up +needing option 5 anyway. + +|4 (meta-lemma) |Discarded — proved non-viable in session 4 sub-addendum +(above). + +|*5a (level-split, intro at L1, elim at L2)* |*Picked.* Smallest delta +from current main. Design-doc-aligned. + +|5b (both intro and elim at L2) |Discarded — retracts PR #204’s +already-merged L1 landing; higher churn for marginal architectural +neatness. +|=== + +==== Owner directive compliance + +Per CLAUDE.md owner directive 2026-05-27: + +* ✅ Zero new `+Admitted.+` or `+Axiom.+` declarations planned for slice +3. +* ✅ No patching of `+Semantics.v+` `+preservation+` (provably false). +* ✅ No patching of legacy `+Typing.v+` judgment. +* ✅ `+Counterexample.v+` regression theorem untouched. +* ✅ Honest accounting of the slice 4b gap (not closed by option 5a; +carried forward into `+preservation_l2+`). +* ✅ All commits GPG-signed. +* ✅ Auto-merge ON for every PR. diff --git a/formal/PHASE-D-REDESIGN.md b/formal/PHASE-D-REDESIGN.md deleted file mode 100644 index 4c904dc3..00000000 --- a/formal/PHASE-D-REDESIGN.md +++ /dev/null @@ -1,482 +0,0 @@ - - - - -# Phase D — L2 effect-typed TFun, redesign memo (2026-05-28) - -This memo refines the Phase D plan in -[`PRESERVATION-DESIGN.md §5.1`](PRESERVATION-DESIGN.md) based on -analytical findings from the 2026-05-28 implementation attempt. -**Read this before continuing Phase D work; it supersedes earlier -slice-by-slice framings that omit the substitution-lemma side -condition introduced below.** - -## What landed - -Two PRs landed on `main` during the 2026-05-28 session as part of -Phase D's scaffolding plus an independent reformulation: - -| PR | Title | What | -|---|---|---| -| #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. | -| #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.** | - -## What we tried for slice 2 — and what broke - -The 2026-05-28 slice 2 attempt added two new typing rules to -`TypingL1.v`: - -```coq -| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, - R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> - R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G - -| T_Lam_L1_Affine_Eff : forall R G T1 T2 e u R_in R_out, - R_in ; ctx_extend G T1 |=L1[Affine] e : T2 -| R_out ; (T1, u) :: G -> - R ; G |=L1[Affine] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G -``` - -Adding new constructors to `has_type_l1` forces every existing -inductive lemma over `has_type_l1` to cover the new cases. The -following lemmas were exercised: - -| Lemma | T_Lam_L1_*_Eff case | Outcome | -|---|---|---| -| `count_occ_le_l1_m` | R-preserving outer | (not hit in this attempt; would close trivially) | -| `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.` | -| `shift_typing_gen_l1_m` | Body's context extends by T1; shift by (S k) | ✅ closed via same pattern as legacy T_Lam | -| `subst_typing_gen_l1_m` | **Substituted value lives at outer R, body at R_in** | 🛑 **STRUCTURAL FAIL** | - -The substitution lemma's `T_Lam_L1_Linear_Eff` case decomposes as: - -* Hypothesis: `Hregv : In rv R` (substituted value's region is in the - outer environment). -* Goal: produce a body derivation at `R_in` with `rv` substituted in. - The body's typing rules (T_Loc_L1, etc.) for occurrences of the - substituted variable require `In rv R_in`. -* Available: nothing that ties `R` to `R_in`. - -**`In rv R` does not imply `In rv R_in`** under the current rule -shape. The body's `R_in` is a free parameter of the lambda type — the -lambda value can declare any `R_in` it likes. There is no enforced -connection between `R_in` and the regions of the bound variable's -type at lambda formation. Substitution lemma is therefore not -generalisable to the new constructors without an additional -invariant. - -## The structural insight - -This is the *same* missing invariant we hit at: - -* `Semantics_L1.v:553/621` (Phase C — shadowed `T_Region_Active_L1`) -* `Semantics_L1.v:1694` (`preservation_l1` lambda-rigidity) -* Sub-sub-case (i) of Phase C's option (c) attempt - (`feedback`: body-input-shrinkage is not a theorem in general) - -Each instance is the L1 vocabulary lacking a way to relate the -**outer** region environment (where caller values live) to a -**nested** region environment (where a lambda body, or a region's -scoped body, operates). The L1 rule shapes treat outer and inner -environments as independent, but **substitution and call sites -require a connection**. - -## The redesign — side condition on `T_Lam_L1_*_Eff` - -Add an explicit side condition to `T_Lam_L1_*_Eff` requiring the -bound variable's regions to be live in the body's input environment: - -```coq -| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, - (forall r, In r (Typing.free_regions T1) -> In r R_in) -> (* NEW *) - R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> - R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G - -| T_Lam_L1_Affine_Eff : forall R G T1 T2 e u R_in R_out, - (forall r, In r (Typing.free_regions T1) -> In r R_in) -> (* NEW *) - R_in ; ctx_extend G T1 |=L1[Affine] e : T2 -| R_out ; (T1, u) :: G -> - R ; G |=L1[Affine] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G -``` - -**Interpretation**: any region appearing in the bound variable's -type `T1` is guaranteed to be live in the body's input environment -`R_in`. When substituting a value `v : T1` with `free_regions T1 ⊇ {rv}` -into the body, `In rv R_in` follows from the side condition. - -For typical lambda types: - -* `T1 = TBase TUnit`: `free_regions T1 = []`. Side condition vacuous. - No constraint on R_in. ✓ -* `T1 = TString rv`: `free_regions T1 = [rv]`. Side condition forces - `In rv R_in`. ✓ -* `T1 = TPair (TString r1) (TString r2)`: forces both `r1` and `r2` - in R_in. ✓ -* `T1 = TFun T1' T2'`: forces all free regions of T1' and T2' into - R_in. ✓ (Closures over substring-typed values carry their region - requirements.) - -This side condition is **discharge-able at lambda formation time**: -the caller forming the lambda must establish the constraint. In -practice this is automatic — any program that types a lambda body -referencing a region in T1 already has that region in scope. - -## Why this closes `subst_typing_gen_l1_m` - -In the T_Lam_L1_Linear_Eff case of the substitution lemma: - -* Hypothesis from rule: `H : forall r, In r (free_regions T1) -> In r R_in`. -* The substituted value `vv` has `In rv R` (lemma's preexisting - Hregv) AND `vv : T1` with `In rv (free_regions T1)` (since - `linear_value_is_loc_l1` constrains rv to be T1's region). -* Apply H: `In rv R_in`. ✓ -* Body typing after substitution uses `In rv R_in` to discharge - T_Loc_L1's `In r R` premise at the substituted variable's - occurrences. - -The lemma case closes via the same pattern as legacy T_Lam_L1_Linear -but threaded through R_in instead of outer R. - -## Slice plan (revised) - -| Slice | Scope | Status | -|---|---|---| -| 1 | TFunEff syntax (`Syntax.v` + `free_regions`) | ✅ MERGED PR #200 | -| 2 (redesigned) | `T_Lam_L1_*_Eff` with side condition `R ⊆ R_in`; cascade through 3 inductive lemmas in `Semantics_L1.v` | ✅ MERGED PR #204 | -| 3 — first attempt | `T_App_L1_Eff` with explicit count-le premise | ⚠️ **REVERTED 2026-05-28** — see addendum below | -| 3 — redesign | TBD — see addendum below | Pending | -| 4 | `preservation_l1` lambda-rigidity closure (`Semantics_L1.v:1694`) | Pending | -| 5 | Phase B Slice 1 (TEcho linearity wire) + Phase C (list-vs-multiset bridge) unlocks | Pending | - -## Slice 3 addendum — type-embedded R blocks env-shrinkage (2026-05-28) - -The first slice 3 attempt added the rule - -```coq -| T_App_L1_Eff : forall m R R_pre G G' G'' e1 e2 T1 T2 R_in R_out, - R ; G |=L1[m] e1 : TFunEff T1 T2 R_in R_out -| R_pre ; G' -> - R_pre ; G' |=L1[m] e2 : T1 -| R_in ; G'' -> - (forall r, count_occ string_dec R_out r <= count_occ string_dec R_in r) -> - R ; G |=L1[m] EApp e1 e2 : T2 -| R_out ; G'' -``` - -`count_occ_le_l1_m`'s case closed via the count-le premise + the IHs. `region_shrink_preserves_typing_l1_gen_m`'s case did NOT close, exposing the deeper structural issue. - -**The issue:** TFunEff embeds R_in / R_out in the type T. When the env-shrinkage lemma removes one occurrence of `rr` from the outer R, the rule's structure forces: - -* e1's TFunEff type is preserved (lemma signature: `T` unchanged). -* e2's IH gives output `remove_first rr R_in` (shrunk version of e2's original output). -* The rule's `R_in` premise is `R_in` (from e1's TFunEff, unchanged). - -These mismatch unless `rr ∉ R_in`. Similarly the lemma's expected output `remove_first rr R_out` vs the rule's output `R_out` requires `rr ∉ R_out`. The natural precondition `~ In rr (free_regions T)` would give both (`free_regions (TFunEff T1 T2 R_in R_out) ⊇ R_in ∪ R_out`). - -**Adding the precondition cascades:** the lemma's induction would need every sub-IH to also satisfy a type-precondition for the sub-expression's type. For `T_App_L1`, e2's type is `T1` (the argument type), which is NOT constrained by the parent's HnotT (parent only constrains `T2`, the return type). So `T_App_L1`'s case BREAKS under the strengthened precondition. - -Strengthening further (e.g., "no intermediate type contains `rr`") is not a clean Prop and would need a meta-condition over derivations. - -**Decision:** REVERT slice 3's `T_App_L1_Eff`. The rule is too tightly coupled to env-shrinkage with the current `region_shrink_preserves_typing_l1_gen_m` signature. - -**Future redesign options for slice 3:** - -1. **Lift env-shrinkage out of the lemma's general statement.** Split `region_shrink_preserves_typing_l1_gen_m` into (i) a value-restricted shape that handles TFunEff vacuously (no T_App in value derivations), and (ii) a non-value shape with TFunEff *explicitly forbidden* via a type-shape predicate. Application sites of `_gen_m` choose which version they need. -2. **Make `T_App_L1_Eff`'s premise pass through an explicit "env-frame" rather than the function's R_in/R_out.** I.e., the rule says "given e1 typing at TFunEff with effect (R_in, R_out), and a frame Δ such that the call-site env is `Δ ∪ R_in`, the post-call env is `Δ ∪ R_out`." This decouples the embedded R from the call-site env. Bigger redesign. -3. **Defer `T_App_L1_Eff` indefinitely.** Phase D's main payoff (closing lambda-rigidity at `Semantics_L1.v:1694`) doesn't strictly require `T_App_L1_Eff` if we can argue preservation for β-reduction on `T_Lam_L1_*_Eff`-typed lambdas via a different path. Investigation owed. - -The 2026-05-28 attempt's diff is preserved in git history (commit on the now-deleted branch); see `git log --all` for archaeology. - -## Slice 3 sub-addendum — option 4 (meta-lemma) analysis (2026-05-28, session 4) - -The 2026-05-28 session 3 close-out flagged a fourth option not in the list above and tagged it "UNTRIED — most promising": - -> **Option 4 — Meta-lemma approach.** Strengthen `expr_strictly_free_of_region`'s `ELam` case to recurse into the type annotation: -> ```coq -> | ELam T body => ~ In r (Typing.free_regions T) /\ expr_strictly_free_of_region r body -> ``` -> Add a context-freedom precondition `forall i T u, ctx_lookup G i = Some (T, u) -> ~ In r (free_regions T)` to `region_shrink_preserves_typing_l1_gen_m`. Then prove a meta-lemma `strict_free_implies_type_free` that derives `~ In rr (free_regions T)` from `expr_strictly_free_of_region rr e` + the typing derivation + context-freedom. The shrinkage lemma's `T_App_L1_Eff` case closes by applying the meta-lemma to e1's TFunEff type, extracting `~ In rr R_in` and `~ In rr R_out`, then mechanically matching the rule's premises with the env-shrunk IHs. - -Session 4 (this addendum) analysed option 4 in detail and **found it provably non-viable**. Recording the analysis here so that future sessions don't re-attempt it. - -### Why the meta-lemma fails at `T_Lam_L1_*_Eff` - -The proposed meta-lemma: - -```coq -strict_free_implies_type_free : - forall rr e R G T R' G' m, - ctx_free_of rr G -> (* context-freedom precondition *) - expr_strictly_free_of_region rr e -> - R ; G |=L1[m] e : T -| R' ; G' -> - ~ In rr (free_regions T) -``` - -would induct on the typing derivation. The `T_Lam_L1_Linear_Eff` case (the rule that landed in slice 2, `TypingL1.v:221-224`) has the shape: - -```coq -| T_Lam_L1_Linear_Eff : forall R G T1 T2 e R_in R_out, - (forall r, In r R -> In r R_in) -> - R_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| R_out ; (T1, true) :: G -> - R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 R_in R_out -| R ; G -``` - -In the meta-lemma's induction at this case: - -* Expression: `ELam T1 body`. -* Strengthened strict-free of `ELam T1 body` decomposes (per the proposed strengthening) to `~ In rr (free_regions T1) /\ expr_strictly_free_of_region rr body`. -* Conclusion type: `TFunEff T1 T2 R_in R_out`. Its `free_regions` (per `Typing.v:55-71`, including the slice 1 case `TFunEff T1 T2 R_in R_out => free_regions T1 ++ free_regions T2 ++ R_in ++ R_out`) is the four-way concatenation. -* The goal `~ In rr (free_regions (TFunEff T1 T2 R_in R_out))` decomposes into four conjuncts: - - `~ In rr (free_regions T1)` ← available from the strengthened strict-free hypothesis. ✓ - - `~ In rr (free_regions T2)` ← available from the IH on `body` (typed at `T2` in the extended context, with the strengthened strict-free of `body`, and context-freedom of the extended `ctx_extend G T1` derivable from outer context-freedom + the first conjunct). ✓ - - `~ In rr R_in` ← **NO SOURCE.** - - `~ In rr R_out` ← **NO SOURCE.** - -`R_in` and `R_out` are **free parameters of the rule**. They appear in the conclusion type but are not constrained by anything in the lambda's syntactic form. The rule's side condition `forall r, In r R -> In r R_in` is unidirectional (R ⊆ R_in), so it gives `In r R → In r R_in`, not `~ In r R_in → ~ In r R`. The body's typing premise (`R_in ; ... |- body : T2 -| R_out`) likewise constrains R_in/R_out only by what the body needs, not by what `rr` doesn't touch. - -In short: **the strengthened strict-free predicate covers syntactic occurrences of `rr` in `ELam T1 body` (which is just T1 and body), but cannot reach `R_in` and `R_out` because those live only in the *typing derivation*, not in the *expression*.** - -### Why this is the same impedance mismatch as the original slice 3 blocker - -The original slice 3 blocker (session 3, this document above) was: `region_shrink_preserves_typing_l1_gen_m`'s `T_App_L1_Eff` case couldn't close because the IH on `e2` gives `remove_first rr R_in` while the rule's premise demands `R_in`. - -The proposed fix via option 4 (meta-lemma) **relocates the same mismatch** to a different proof obligation: the meta-lemma's `T_Lam_L1_*_Eff` case can't close because `R_in`/`R_out` are syntax-invisible free parameters. - -The root cause is the same in both presentations: **the L1 vocabulary of `expr_strictly_free_of_region` cannot constrain the effect-typing annotations carried in `TFunEff`**, because effect annotations are a *typing* artifact, not a *syntactic* one. - -### Adjacent options also fail for the same reason - -Two repair attempts on option 4 also fail: - -1. **Require `R_in ⊆ free_regions T1 ∪ free_regions T2`** as an extra side condition on `T_Lam_L1_*_Eff`. This would let `~ In rr T1` + `~ In rr T2` (from strict-free + IH) imply `~ In rr R_in`. **Problem**: this over-constrains R_in. A lambda body that uses regions *not* in its parameter or return type (e.g., a lambda that allocates a transient region for its body's local work, drops it before returning) would no longer type — its R_in legitimately exceeds the type's free regions. -2. **Require `R_in` to be minimal** (exactly the regions the body actually uses, derivable as a function of body syntax). **Problem**: this requires a "region inference" function that is decidable and structurally faithful. The current ephapax grammar doesn't expose enough structure at lambda formation to compute this; we'd need annotations or a fresh inference pass. - -Both repairs require slice 2's already-landed rule shape to change. Even granting the change, they trade off compositionality (call sites that wrap lambdas with extra-region preludes/postludes break under a minimal-R_in regime). - -### Implication for the slice 3 redesign - -Option 4 is removed from the candidate list. The remaining candidates are: - -1. **Split lemma** (option 1) — value-restricted vs non-value variants of `region_shrink_preserves_typing_l1_gen_m`. **Caveat surfaced in session 4**: the value-restricted variant's `T_Lam_L1_*` cases still recurse into non-value bodies via IH, so the non-value variant must exist and must handle `T_App_L1_Eff`. The split doesn't make `T_App_L1_Eff`'s case go away; it just moves it. -2. **Env-frame Δ** (option 2) — restructure `T_App_L1_Eff`'s premise so the call-site env is `Δ ++ R_in` and the post-call env is `Δ ++ R_out`. **Caveat surfaced in session 4**: `remove_first` on `Δ ++ R_in` still removes from R_in if `rr ∈ R_in`. The frame Δ doesn't shield R_in from env shrinkage unless we further require `rr ∉ R_in`. The deeper fix is still needed. -3. **Defer `T_App_L1_Eff` indefinitely** (option 3) — explicitly accept that TFunEff lambdas can be formed but never called via `has_type_l1`. **Status from session 3**: rejected because slice 4 (preservation_l1 lambda-rigidity closure) requires β-reduction typing for TFunEff lambdas. **Status from session 4**: re-examined and the rejection still holds, but with one nuance — preservation_l1's `S_App_Step2` case can be made vacuous for TFunEff function values *if* we can prove "an `EApp v1 e2` with `v1 : TFunEff T1 T2 R_in R_out` does not type via `has_type_l1`" as a meta-fact. This is true (no T_App rule produces a TFunEff conclusion at the function position), so option 3 is *partially* viable for slice 4: it closes lambda-rigidity for *uncallable* TFunEff lambdas. Slice 5's broader effect-typing work would still need T_App_L1_Eff or a successor. - -### Option 5 — level-split - -A new option surfaces from the session 4 analysis. The structural insight is that **T_App_L1_Eff is doing L2 work** (calling an effect-typed function is fundamentally a modality/effect operation, not a region operation), but it currently lives in L1 (`has_type_l1`). Move it to L2: - -* `has_type_l1` (L1) — knows `TFunEff` exists as a type former (slice 1, already landed) and can type lambdas at it (slice 2, already landed), but **does not provide an application rule for TFunEff**. EApp on a TFunEff function does not type at L1. -* `has_type_l2` (L2) — wraps `has_type_l1` with a `T_App_L2_Eff` rule that handles application of TFunEff lambdas. The L2 judgment becomes the canonical home for effect-typed application. - -Properties: - -* `region_shrink_preserves_typing_l1_gen_m` only needs to handle the L1 judgment, where T_App_L1_Eff doesn't exist. Slice 3's blocker disappears at the L1 level. -* `preservation_l1` lambda-rigidity closure (slice 4): TFunEff lambdas are still uncallable at L1, but L1 was never the right venue for their β-reduction. Lambda-rigidity at the L1 level closes for *legacy* lambdas via existing means; TFunEff lambdas are inert at L1, and inert values preserve trivially. -* `preservation_l2`: a new theorem (slice 5 work) handles β-reduction for TFunEff lambdas under the L2 judgment. The L2 vocabulary can introduce effect-aware env-shrinkage lemmas designed for TFunEff from the start, without retrofitting an L1 lemma. - -Trade-off: option 5 splits T_Lam_L1_*_Eff (L1) from T_App_L2_Eff (L2). The introduction and elimination of TFunEff live at different layers. This is unusual for type formers but defensible because the introduction is "syntactically lambda-shaped" (an L1 syntactic form with extra type-level annotations) while elimination is "effect-semantic" (consuming the effect annotations meaningfully). The current `TypingL2.v` is described as a "thin wrapper through `TypingL1.has_type_l1`"; option 5 thickens it with at least the T_App_L2_Eff rule. - -### Recommendation for owner decision - -Session 4 cannot pick among options 1, 2, 3, and 5 without owner input — the choice is a layer-design decision, not a proof-tactics decision. Each option has a different downstream cost profile: - -| Option | Slice 3 cost | Slice 4 closure | Slice 5 unblock | -|---|---|---|---| -| 1 (split lemma) | High (rewrite `_gen_m`; non-value variant still blocked) | Partial | Unclear | -| 2 (env-frame Δ) | Medium-high (`T_App_L1_Eff` redesign + cascade) | Should close | Unclear; might cascade-break Phase B Slice 1 | -| 3 (defer) | Low (no code change) | Closes for legacy lambdas only | Blocked until 5 lands | -| 5 (level-split) | Medium (new T_App_L2_Eff in `TypingL2.v`; thicken L2 wrapper) | Closes at L1 (TFunEff inert at L1) | Natural L2 venue for full effect typing | - -Per the CLAUDE.md owner directive "DO escalate before patching": this addendum **escalates the slice 3 redesign decision to the owner**. No code lands until the option is selected. - -### What does NOT change - -* PR #200 (TFunEff syntax) stays. -* PR #201 (strict predicate, blocker 5 closure) stays. -* PR #203 (this design memo) stays. -* PR #204 (T_Lam_L1_*_Eff rules + R ⊆ R_in side condition cascade) stays. -* PR #205 (slice 3 first attempt findings) stays. -* Counterexample.v (legacy preservation is false) is untouched in all four options. - -**What slice 3 was supposed to unlock:** β-reduction for `T_Lam_L1_*_Eff`-typed lambdas (preservation_l1 case S_App_Fun) would use `T_App_L1_Eff`'s typing. Without `T_App_L1_Eff`, TFunEff lambdas can be *formed* but never *called* via has_type_l1 — slice 2's contribution is preserved but standalone (no programs exercise it yet). - -The strict-predicate reformulation (PR #201) is independent of these -slices and already merged. - -## Implementation notes for slice 2 - -1. The 2 rules: add right after `T_Lam_L1_Affine` at - `TypingL1.v:183` (verified compile order works). -2. The side condition is a `forall r, In r (free_regions T1) -> In r R_in` - Prop — straightforward to discharge in induction cases. -3. **Cascade to 8 inductive Qed lemmas** in `Semantics_L1.v`: - - `count_occ_le_l1_m` — trivial (R-preserving outer) - - `region_shrink_preserves_typing_l1_gen_m` — re-apply rule with - body unchanged + side condition unchanged (region shrink at - outer doesn't touch the body's premise) - - `typing_preserves_length_l1` — trivial - - `typing_preserves_bindings_l1` — mirrors legacy T_Lam case - - `unrestricted_flag_unchanged_l1` — similar - - `shift_typing_gen_l1_m` — mirrors legacy T_Lam case - - `value_R_G_preserving_l1` — lambda is a value; R-preserving - case trivial - - `subst_typing_gen_l1_m` — **uses the side condition** to discharge - `In rv R_in` from `In rv (free_regions T1)` -4. `Counterexample.v` is unaffected (no TFunEff or T_Lam_L1_*_Eff - usage). - -## Owner directive compliance - -Per CLAUDE.md owner directive 2026-05-27: - -* ✅ Zero new `Admitted.` or `Axiom.` declarations -* ✅ No patching of `Semantics.v` `preservation` (provably false) -* ✅ No patching of legacy `Typing.v` judgment -* ✅ Counterexample.v regression theorem untouched -* ✅ All commits GPG-signed -* ✅ Auto-merge ON for every PR - -## Anti-patterns this redesign avoids - -* **Strengthening `subst_typing_gen_l1_m`'s preconditions** with a - per-call-site `In rv R_in` hypothesis would cascade to every - caller and force them to prove the connection ad-hoc. The side - condition at rule formation localises the constraint. -* **Separate `has_type_l1_eff` judgment** would double the lemma - surface and require bridge lemmas. Avoided by extending - `has_type_l1` in place with a clean side condition. -* **Constraining R_in = outer R** would defeat the purpose of - effect-typing. Avoided. - -## Cross-references - -* [`PRESERVATION-DESIGN.md §5.1`](PRESERVATION-DESIGN.md) — original - Phase D outline. -* [`PROOF-NEEDS.md`](../PROOF-NEEDS.md) — proof debt audit (Phase B - + Phase C deferral rows reference this Phase D closure). -* `Semantics_L1.v:553/621` — Phase C structural admits that close - once slice 4+5 land. -* `Semantics_L1.v:1694` — `preservation_l1` lambda-rigidity admit - that closes at slice 4. - -## Future re-attempts of perm_l1 (agent finding 2026-05-28) - -The standalone `region_env_perm_typing_l1` (existential output) -agent attempt 2026-05-28 found 7 admits across two failure families: - -1. R-stability rules (`T_Lam_L1_*`, `T_Borrow_Val_L1`, `T_Echo_L1`) - — force output_R = input_R as lists; IH gives perm-equiv not - list-equal. -2. Branch-agreement rules (`T_Case_L1_*`, `T_If_L1_*`) — branches - yield outputs perm-equiv to a join but not list-equal. - -Both families dissolve once the L2 effect-typed structure is in -place: R-stability rules get effect-typed wrappers that don't force -list equality, and branch-agreement gets a per-branch effect that -the join sums. - -`perm_l1` is therefore unblocked at slice 5 (post-T_App_L1_Eff -landing), not at slice 2. - -## Slice 3 sub-sub-addendum — option 5a picked, slice plan revised (2026-05-28, session 5) - -The owner picked **option 5a** (level-split, intro stays at L1, elim moves to L2) on 2026-05-28 after the session 4 analysis (slice 3 sub-addendum above) and a session 5 design-doc cross-check. - -### Why 5a is the right call — design-doc alignment - -`PRESERVATION-DESIGN.md` §5.1 lines 468-474 already endorse the L1-intro / L2-elim split explicitly: - -> **Why this isn't L1's job.** Effect-typed function types are a typing-layer property, not a region-layer property. Adding them to L1's unparameterised judgment would conflate the two. **L2 is the natural home**: the modality parameter is *already* a typing-layer decoration; the effect annotation rides alongside it. **After L2's effect-typed TFun lands, L1's gap closes by importation, not by re-deriving L1.** - -The doc consistently names the introduction rule `T_Lam_L1_Linear` / `T_Lam_L1_Affine` with the `_L1_` infix, confirming that intro lives in L1. Option 5a is therefore not an architectural departure — it returns the slice plan to the design-doc-prescribed line, which session 3's first slice-3 attempt (T_App_L1_Eff in `has_type_l1`) had deviated from. - -§10's implementation order ("each step's correctness is independent of the next") imposes no sequencing constraint that blocks 5a. - -### 5a vs 5b - -Two sub-variants of option 5 exist: - -| Sub-option | What | Disposition | -|---|---|---| -| **5a (picked)** | Intro stays at L1 via PR #204; elim adds `T_App_L2_Eff` to `has_type_l2`. | **Picked.** Smallest delta from current main. Design-doc-aligned. | -| 5b | Retract PR #204's L1 intro landing; move both intro and elim into L2. | Discarded — retracts an already-merged PR for marginal architectural neatness; trades real churn for theoretical symmetry. | - -### What changes vs the original slice plan - -| Slice | Original plan | Option 5a revised plan | -|---|---|---| -| 1 | TFunEff syntax | Unchanged. ✅ MERGED PR #200 | -| 2 | `T_Lam_L1_*_Eff` with R ⊆ R_in side condition | Unchanged — *but reframed as "inert introduction at L1"*. ✅ MERGED PR #204 | -| 3 | `T_App_L1_Eff` in `has_type_l1` | **MOVED**: `T_App_L2_Eff` lands in `has_type_l2` (TypingL2.v) as a sibling of `L2_lift_l1`. | -| 4 | `preservation_l1` lambda-rigidity closure | **SPLIT** into 4a (effect-typed path) + 4b (legacy path) — see below. | -| 5 | Broader effect-typing + Phase B/C unblocks | Naturally hosted in L2 with effect-aware lemmas; `preservation_l2` is the new theorem target. | - -### Slice 4 split — what option 5a does and does not close at L1 - -The session 4 addendum noted that under option 5a, "TFunEff lambdas are inert at L1, and inert values preserve trivially." A session 5 subagent verification confirmed this holds for *one of two* sub-cases inside `preservation_l1`'s `S_App_Step2`: - -- **Slice 4a — TFunEff lambdas (effect-typed path).** Under option 5a there is no `T_App_L1_Eff` rule, and the legacy `T_App_L1` at `TypingL1.v:231-234` requires `e1 : TFun T1 T2`, NOT `TFunEff`. No coercion bridge exists (`linear_to_affine` is structural; T_Var/T_Loc cannot hide TFunEff). Therefore any `EApp v1 e2` derivation at L1 with `v1` typed via `T_Lam_L1_*_Eff` is vacuous: no L1 derivation reaches this case. The corresponding sub-case in `preservation_l1` closes by inversion vacuity. **No new proof work needed.** - -- **Slice 4b — legacy `TFun` lambdas (body-R-rigidity path).** The legacy `T_Lam_L1_Linear` / `T_Lam_L1_Affine` rules at `TypingL1.v:177-184` still produce `TFun T1 T2`, and `T_App_L1` at `TypingL1.v:231-234` still accepts them. The body-R-rigidity gap documented in `Semantics_L1.v:1708-1713` *remains* for these lambdas under option 5a — option 5a's level-split does **not** add structural information that closes legacy `TFun` lambda-body preservation across an R-shift. **This sub-case stays gated.** - -Implication: `preservation_l1` as currently stated (over the whole `has_type_l1` judgment) cannot Qed under option 5a alone. The natural closure venue is `preservation_l2` — a new theorem stated over `has_type_l2`, where effect-typed paths are fully covered and legacy `TFun` paths are honestly carried forward via L1 importation (with the legacy body-R-rigidity admit recorded as inherited debt rather than concealed). - -### The T_App_L2_Eff rule design - -The L2 elimination rule mirrors `T_App_L1` but with effect threading through R_in/R_out: - -```coq -| T_App_L2_Eff : forall m R R1 G G' G'' e1 e2 T1 T2 R_in R_out, - has_type_l2 m R G e1 (TFunEff T1 T2 R_in R_out) R1 G' -> - has_type_l2 m R1 G' e2 T1 R_in G'' -> - has_type_l2 m R G (EApp e1 e2) T2 R_out G'' -``` - -Reading: e1 produces an effect-typed lambda (consuming R → R1, where R1 is the intermediate env after e1's evaluation). e2 evaluates the argument, threading R1 → R_in (the lambda's input expectation). The lambda body then runs, consuming R_in and producing R_out. The whole `EApp` expression's output env is R_out. - -The e1 sub-derivation at type `TFunEff T1 T2 R_in R_out` lifts from an L1 derivation via `L2_lift_l1` (since `T_Lam_L1_*_Eff` rules are still the source of TFunEff typings — option 5a keeps intro at L1). - -**Side condition discharge**: PR #204's `(forall r, In r R -> In r R_in)` constraint (R ⊆ R_in on the lambda's input env) was the load-bearing invariant for slice 2's substitution closure. At elimination time (T_App_L2_Eff), this constraint flows naturally — the lambda was formed at R ⊆ R_in, and the call site supplies R_in directly via e2's output env. No new side condition is needed at elimination. - -### Implementation surface (verified by session 5 subagent audit) - -`formal/TypingL2.v` currently has a single constructor `L2_lift_l1` at lines 85-91 — a modality-indexed lift-only inductive ready for thickening. Adding `T_App_L2_Eff` as a sibling constructor (insertion point: after line 91) is additive and has zero downstream consumers in `formal/*.v` (only the file itself references `has_type_l2`). Existing utilities (`weaken_modality`, `weaken_modality_le`, `lift_l1_to_*`, `project_l2_to_l1`) continue to work — they bridge via `L2_lift_l1` for L1-derived facts and the new constructor adds an independent path for T_App_L2_Eff derivations. - -### Slice plan (post-option-5a) - -| Slice | Scope | Status | -|---|---|---| -| 1 | TFunEff syntax | ✅ MERGED PR #200 | -| 2 | `T_Lam_L1_*_Eff` rules with R ⊆ R_in side condition | ✅ MERGED PR #204 | -| 3 (option 5a) | `T_App_L2_Eff` in TypingL2.v as a sibling constructor to `L2_lift_l1` | **Next** | -| 4a | TFunEff path inert-at-L1 (closes by inversion vacuity) | Bundled with slice 5 (`preservation_l2`) | -| 4b | Legacy `TFun` body-R-rigidity at preservation_l1 | **Stays gated**; honest admit carried forward into `preservation_l2` via L1 importation | -| 5 | `preservation_l2` (new theorem); broader effect-typing; Phase B/C unblocks | After slice 3 | - -### What does NOT change - -* PR #200 (TFunEff syntax) — unchanged. -* PR #201 (strict predicate, blocker 5 closure) — unchanged. -* PR #203 (Phase D memo) — unchanged. -* PR #204 (T_Lam_L1_*_Eff rules) — unchanged. Reframed as "inert introduction at L1." -* PR #205 (slice 3 first attempt findings) — unchanged (archaeology). -* PR #207 (slice 3 sub-addendum, option 4 non-viability) — unchanged. -* Counterexample.v — untouched. - -### Disposition of session 4's candidate matrix - -| Option | Disposition | -|---|---| -| 1 (split lemma) | Discarded — non-value variant still blocked per session 4 caveat. | -| 2 (env-frame Δ) | Discarded — `remove_first` still cuts R_in per session 4 caveat. | -| 3 (defer) | Discarded — punts the design question; slice 5 ends up needing option 5 anyway. | -| 4 (meta-lemma) | Discarded — proved non-viable in session 4 sub-addendum (above). | -| **5a (level-split, intro at L1, elim at L2)** | **Picked.** Smallest delta from current main. Design-doc-aligned. | -| 5b (both intro and elim at L2) | Discarded — retracts PR #204's already-merged L1 landing; higher churn for marginal architectural neatness. | - -### Owner directive compliance - -Per CLAUDE.md owner directive 2026-05-27: - -* ✅ Zero new `Admitted.` or `Axiom.` declarations planned for slice 3. -* ✅ No patching of `Semantics.v` `preservation` (provably false). -* ✅ No patching of legacy `Typing.v` judgment. -* ✅ `Counterexample.v` regression theorem untouched. -* ✅ Honest accounting of the slice 4b gap (not closed by option 5a; carried forward into `preservation_l2`). -* ✅ All commits GPG-signed. -* ✅ Auto-merge ON for every PR. diff --git a/formal/PRESERVATION-DESIGN.adoc b/formal/PRESERVATION-DESIGN.adoc new file mode 100644 index 00000000..87be39cd --- /dev/null +++ b/formal/PRESERVATION-DESIGN.adoc @@ -0,0 +1,1508 @@ +== Preservation: principled redesign + +Companion to `+PRESERVATION-HANDOFF.md+`. The handoff document is a +diagnostic record of attempted proof-engineering. This document is the +*design* rationale for the typing-layer change that the verified +counterexample (`+Counterexample.v+`, all three lemmas `+Qed.+`) now +makes unavoidable. + +The handoff’s Option 3 ("`type-system change`") is here re-cast not as a +patch but as the deliberate separation of four concerns that the current +calculus conflates: *structural discipline*, *region capability +tracking*, *dyadic interaction semantics*, and *echo / residue +semantics*. Preservation is derived from explicit invariants in the new +architecture; it is not forced through the old. + +''''' + +=== 1. What the counterexample proves + +`+formal/Counterexample.v+` exhibits a configuration where the calculus +admits a single-step reduction whose result is _untypable_ at the same +outer type — i.e. preservation as stated is *false*, not unproven. + +[width="100%",cols="50%,50%",options="header",] +|=== +|Input |Type at `+R_in = [r0; r1]+` +|`+EPair (ERegion r1 (ELoc l0 r0)) (ELoc l1 r1)+` +|`+TProd (TString r0) (TString r1)+` +|=== + +After one `+S_Pair_Step1+` (lifting `+S_Region_Exit+` on the first +child): + +[width="100%",cols="50%,50%",options="header",] +|=== +|Output |Type at `+R' = [r0]+` +|`+EPair (ELoc l0 r0) (ELoc l1 r1)+` |*none* (sibling `+ELoc l1 r1+` +requires `+In r1 [r0]+`, false) +|=== + +==== The missing invariant, named + +The sibling `+ELoc l1 r1+` was typed under the assumption that `+r1+` +would be live. The first child _invalidated that assumption_ by exiting +`+r1+` mid-evaluation. The typing judgment has no place to record that +the input region environment for the second sibling depends on the +output region environment of the first. + +____ +*Missing invariant — region capability monotonicity per sub-expression*: +for any compound form `+C(e₁, …, eₙ)+`, the capability environment in +which `+e_{i+1}+` is typed equals the capability environment _left over_ +after `+e_i+` evaluates. +____ + +The current rules thread the _linearity_ context `+G+` left-to-right +through compound rules, but the _region_ environment `+R+` is shared +statically by all siblings. That asymmetry is the bug. + +''''' + +=== 2. Why a side-condition patch is wrong + +The handoff lists three minimal-patch candidates: mutual induction, +inversion-on-`+Hstep+` structural recursion (~150 LOC helper), or an +ad-hoc sibling-region-disjointness side condition on `+T_Pair+`, +`+T_Let+`, `+T_App+`, …. + +All three preserve the architectural defect (R as a static, sibling- +shared parameter) and force the proof through it. The disjointness side +condition is the worst of the three because it scatters the invariant +across every compound rule, making future rules (Echo Types, +effect/capability extensions, dyadic interaction primitives) responsible +for re-discovering and re-imposing the same constraint at every +introduction. + +The principled fix is to thread R the same way G is threaded. The +sibling-region-disjointness _property_ then *follows as a corollary* of +the new threading; it is no longer a rule premise. + +''''' + +=== 3. The four orthogonal concerns + +The redesign separates four discipline layers that the current calculus +braids together: + +[width="100%",cols="25%,25%,25%,25%",options="header",] +|=== +|Layer |Concern |What it tracks |Encoded as +|*L1* |Region capabilities |Which regions are currently live; how they +shrink under reduction |Input/output capability sets `+R_in → R_out+` +threaded through every rule + +|*L2* |Structural discipline |Whether linear bindings must be consumed +(Linear) or may be implicitly dropped (Affine) |Modality parameter +`+ℓ ∈ {Linear, Affine}+` on the judgment + a thin-poset weakening +`+Linear ⇒ Affine+` + +|*L3* |Echo / residue |Whether irreversible operations (region exit, +drop) produce a residue witness that must (Linear-echo) or may +(Affine-echo) be observed |Echo types +`+Echo f y := Σ A (λ x → f x ≡ y)+` +(https://github.com/hyperpolymath/echo-types/blob/main/proofs/agda/Echo.agda#L14[echo-types/Echo.agda:14]); +residue obligation tracked by a third thin-poset decoration + +|*L4* |Dyadic interaction |The mother–child pairing of the language: +ephapax-linear vs ephapax-affine as observable interaction modes +|Top-level mode declaration; consequences cascade into L2 and L3 +defaults +|=== + +The key claim — borrowed verbatim from the echo-types calculus +(`+EchoLinear.agda:30-101+`) — is that *decoration commuting* holds: +because each layer’s order is a thin poset (subset on `+R+`, the +two-point modality `+Linear ≤ Affine+`, the residue refinement +`+mandatory ≤ optional+`), composition of layers is definitional. No +coherence burden is introduced; the layers do not have to "`agree`" on +anything beyond their independent invariants. + +This is the architectural payoff: *adding L3 later does not require +reproving anything about L1 and L2.* + +''''' + +=== 4. Layer 1 in detail — the preservation fix + +==== 4.1 Judgment signature + +Current: + +.... +R ; G ⊢ e : T -| G' +.... + +New: + +.... +R ; G ⊢ e : T -| R' ; G' +.... + +`+R'+` is the capability environment *after* `+e+` has reduced to a +value. It is determined syntactically from `+e+` (see §4.3); the change +is syntax-directed, not inferential. + +==== 4.2 Compound rules thread `+R+` left-to-right + +Every compound rule that currently threads `+G+` left-to-right gains the +corresponding `+R+` threading. Example, `+T_Pair+`: + +[source,coq] +---- +| T_Pair : forall R R' R'' G G' G'' e1 e2 T1 T2, + R ; G ⊢ e1 : T1 -| R' ; G' -> + R' ; G' ⊢ e2 : T2 -| R'' ; G'' -> + R ; G ⊢ EPair e1 e2 : TProd T1 T2 -| R'' ; G'' +---- + +Same shape change for `+T_Let+`, `+T_LetLin+`, `+T_App+`, +`+T_StringConcat+`, `+T_If+`, `+T_Case+` (with branches required to +_agree_ on `+R_out+`), `+T_Fst+`, `+T_Snd+`, `+T_Inl+`, `+T_Inr+`, +`+T_Drop+`, `+T_Copy+`, `+T_Borrow_Val+`. + +==== 4.3 Region rules expose the capability shift + +`+T_Loc+` and value rules: `+R_out = R_in+` (values don’t affect +capabilities). + +`+T_Region+` (fresh region introduction; `+~ In r R_in+`): + +[source,coq] +---- +| T_Region : forall R R_body G G' r e T, + ~ In r R -> + ~ In r (free_regions T) -> + (r :: R) ; G ⊢ e : T -| R_body ; G' -> + R ; G ⊢ ERegion r e : T -| remove_first r R_body ; G' +---- + +`+T_Region_Active+` (re-entering an already-live region; `+In r R_in+`): + +[source,coq] +---- +| T_Region_Active : forall R G G' r e T, + In r R -> + ~ In r (free_regions T) -> + R ; G ⊢ e : T -| R ; G' -> (* body must NOT exit r *) + R ; G ⊢ ERegion r e : T -| remove_first r R ; G' +---- + +The two regional rules now make the capability cost of the construct +*explicit* at the typing level. `+S_Region_Exit+`’s operational effect +(shrink `+R+` by one `+r+`) is mirrored by the typing rule’s `+R_out+`. + +==== 4.4 The counterexample no longer types + +Under the new rules: + +* `+ERegion r1 (ELoc l0 r0)+` typed at `+R_in = [r0; r1]+`: by +`+T_Region_Active+` with `+In r1 [r0; r1]+` ✓ and +`+~ In r1 (free_regions (TString r0)) = ~ In r1 [r0]+` ✓. Body +`+ELoc l0 r0+`: `+R_in = [r0; r1]+`, `+R_out = [r0; r1]+`. Outer +`+R_out = remove_first r1 [r0; r1] = [r0]+`. +* Now `+EPair+`’s second sibling `+ELoc l1 r1+` must type at +`+R = [r0]+`. `+T_Loc+` requires `+region_active [r0] r1+`, false. +* *The `+EPair+` rule has no derivation.* Counterexample disappears. + +==== 4.5 Preservation under the new judgment + +The original goal — + +____ +If `+R; G ⊢ e : T -| G'+` and `+(μ, R, e) → (μ', R', e')+` then +`+R'; G ⊢ e' : T -| G'+`. +____ + +— now becomes: + +____ +If `+R; G ⊢ e : T -| R_final; G'+` and `+(μ, R, e) → (μ', R', e')+` then +there exists `+R'_final+` such that `+R'; G ⊢ e' : T -| R'_final; G'+` +and `+R'_final+` is consistent with `+R_final+` (specifically: +`+R_final+` is reachable from `+R'_final+` by zero or more applications +of the remaining region exits in `+e'+`). +____ + +The 11 admits on the `+touches_region+` RIGHT branch dissolve: each +admit was asking "`how do I re-type the unchanged sibling under the +shrunken R’?`". Under the new threading the sibling is typed at `+R'+` +from the outset — there is nothing to re-type. + +==== 4.6 Cost: which existing lemmas need re-proof + +The judgment shape changes, so every lemma that pattern-matches the old +shape (`+R; G |- e : T -| G'+`) must be updated. The lemma count in the +current `+Semantics.v+` is ~80; most are mechanical signature updates. +The *substance* of each proof is unchanged because the new R-threading +is just a parallel copy of the existing G-threading. + +A targeted estimate (the user explicitly de-prioritises patch size, so +this is informational only): + +[width="100%",cols="30%,>40%,30%",options="header",] +|=== +|Lemma class |Count (approx) |Substance change +|`+value_context_unchanged+` and variants |~6 |none — values: +`+R_out = R_in+` + +|`+subst_preserves_typing+`, `+_strong+` |2 |thread `+R+` like `+G+` + +|`+region_shrink_preserves_typing+`, `+_dup+` |2 |restated; some may +become redundant + +|`+region_add_typing+`, `+region_env_perm_typing+` |2 |restated + +|`+step_R_eq_or_touches_region+`, `+step_R_change_shape+` |2 |possibly +subsumed by the new judgment + +|`+step_preserves_type+`, `+step_output_context_eq+` and at-pre helpers +|4 |restate `+step_output_context_eq+` to relate `+R_final+` and +`+R'_final+`; `+step_preserves_type+` proves the new preservation +statement directly + +|`+preservation+` |1 |proved by structural induction on the new judgment +|=== + +The at-pre helper pattern survives because it is orthogonal to the +threading change: the pre-step env is the new `+R_in+` of the diagonal +typing rule. + +==== 4.7 L1 closure status (2026-05-27) + +Six follow-up PRs against `+proof/l1-region-threading-design+` (the +design branch) landed the L1 implementation incrementally: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|PR |Scope |Outcome +|#155 |`+TypingL1.v+` — the R-threaded judgment + counterexample +regression |`+bad_input_untypable_l1+` Qed + +|#157 |`+Semantics_L1.v+` skeleton — `+preservation_l1+` stated +|scaffold + +|#158 (L1.A) |`+value_R_G_preserving_l1+` |Qed + +|#159 (L1.B) |`+region_shrink_preserves_typing_l1+` (routes via +`+_gen+`) |Qed; `+_gen+` Admitted (2 internal admits at the time) + +|#160 (L1.C) |`+subst_preserves_typing_l1+` (strengthened statement) +|Qed; introduced a sub-Axiom for the inner-R retype obligation + +|#161 (L1.D) |`+preservation_l1+` proof body |29/33 cases Qed; 4 admits +surface region-env weakening gap + +|#162 (L1.F) |Discharge unsound `+loc_retype_at_R_l1+` axiom |Original +Axiom replaced by a Qed-able Lemma + a narrower (still admitted) +`+region_liveness_at_split_l1+`; 2 of 11 call sites discharge directly + +|#163 (L1.E) |Region-env weakening attack on the 6 admits (2 in `+_gen+` ++ 4 in `+preservation_l1+`) |2 admits closed via new `+count_occ_le_l1+` +monotonicity lemma; 4 admits remain with structural diagnosis (see +below) +|=== + +After all six PRs land on the design branch: + +* *4 admits remain* in `+Semantics_L1.v+`: +[arabic] +. `+region_shrink_preserves_typing_l1_gen+` line ~390 — +`+T_Region_Active_L1+` shadowed case. List-vs-multiset mismatch: +set-equality bridges don’t preserve `+remove_first_L1+` outputs (the +operation depends on list position, not just membership). +. `+preservation_l1+` `+S_StringConcat_Step2+` — operationally sound; +needs a `+step_pop_disjoint_from_type_l1+` lemma (tractable, deferred to +L1.G). +. `+preservation_l1+` `+S_App_Step2+` — see §4.8 (fundamental soundness +gap). +. `+preservation_l1+` `+S_Pair_Step2+` — see §4.8 (same gap). +* *1 Axiom remains*: `+region_liveness_at_split_l1+` (a narrower, +sound-as-stated statement of what the original unsound +`+loc_retype_at_R_l1+` was reaching for). Three closure paths documented +in-file: strengthen `+T_Var_Lin_L1+`, add a side condition to +`+subst_typing_gen_l1+`, or carry `+In rv R_intermediate+` through the +induction. +* *2 `+Admitted.+` markers* remain +(`+region_shrink_preserves_typing_l1_gen+` and `+preservation_l1+`) — +each closes the moment its internal admits close. + +==== 4.8 Soundness finding: T_Lam_L1 + T_Var_*_L1 are too permissive + +L1.E surfaced a genuine soundness issue, separate from the proof- +engineering gaps: + +*The finding.* `+T_Lam_L1+` rigidly fixes the lambda body’s region +environment `+R+` at lambda-creation time, with no mechanism to retype +the body at a shifted `+R+` before application. Combined with the +orthogonal observation that `+T_Var_Lin_L1+` and `+T_Var_Unr_L1+` permit +typing variables of type `+TString r+` at _any_ `+R+` (including +`+R = []+`), the L1 typing system is genuinely too permissive about +region presence for preservation under `+R+`-shifting steps. + +*Why this matters operationally.* `+S_App_Step2+` evaluates the argument +of `+EApp v_fn e_arg+` when `+v_fn+` is a value (a lambda). The inner +step on `+e_arg+` may shift `+R → R'+` via a region operation. The +lambda’s typing was fixed at the _original_ `+R+`; the rules give no way +to re-derive its typing at `+R'+`. The preservation theorem is therefore +unprovable for these cases under the current rules — not a +proof-engineering gap but a calculus-design gap. + +*Resolution paths* (each independent): + +[arabic] +. *Effect-typed lambdas (L2/L3 territory)*: parameterise function types +over the region-effect they have, so the typing rule sees how the body +changes `+R+` and can re-derive at the call site. +. *Restrict T_Lam_L1*: require the lambda body to be region-pure (no +free regions in `+T1+`, `+T2+`, or the body’s intermediate types). This +is restrictive but unblocks preservation immediately. +. **Restrict T_Var_*_L1**: require `+In r R+` for any variable whose +type contains `+r+`. This propagates the region constraint into the +variable-use side, closing the gap. + +The L1.E PR documents this in-source. None of (1)/(2)/(3) is in scope +for the L1 minimal-fix; each is a follow-up. See ROADMAP for sequencing. + +''''' + +=== 5. Layer 2 in detail — Linear vs Affine modality + +Currently `+is_linear_ty+` and `+T_Drop+`/`+T_Copy+` encode a per-_type_ +distinction (a `+TRef Lin+` is linear, a `+TRef Unr+` is not). The user +clarified that there is a stronger, *language-level* dyadicity: + +* *Ephapax-Linear*: strict exact-consumption semantics. Weakening +forbidden. Drop must be explicit and consumes the value. Every +irreversible operation must produce an observed residue. +* *Ephapax-Affine*: permits implicit drop. Weakening allowed. +Irreversible operations may silently produce a non-duplicable residue +trace. + +Encode the modality as a judgment parameter: + +.... +R ; G ⊢_ℓ e : T -| R' ; G' where ℓ ∈ {Linear, Affine} +.... + +The modality lives on the *judgment*, not on the type. The two +sublanguages share the same syntax (`+Syntax.v+` unchanged) and the same +operational semantics (`+Semantics.v+` unchanged); they differ in which +derivations the typing relation admits. + +==== Linear ⇒ Affine: a thin-poset decoration + +Every Linear derivation is an Affine derivation (Linear is the more +restrictive mode). This is the modality weakening: + +[source,coq] +---- +weaken_modality : forall R G e T R' G', + R ; G ⊢_Linear e : T -| R' ; G' -> + R ; G ⊢_Affine e : T -| R' ; G' +---- + +This mirrors `+EchoLinear.agda:53-58+` +(`+weaken : LEcho linear → LEcho affine+`). Because `+Linear ≤ Affine+` +is propositional, the weakening commutes with R-threading and +G-threading by the decoration-commuting recipe (echo-types’ +`+degradeMode-comp+`, `+EchoLinear.agda:93-101+`). + +==== What changes per mode + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Rule |Linear |Affine +|`+T_Lam+` |`+(T1, true) :: G+` on body output +|`+(T1, true_or_unused) :: G+` + +|Top-level (closed terms) |`+G = G' = []+` required |`+G = []+`, `+G'+` +may carry unused linear bindings + +|`+T_Drop+` |discharges an _obligation_ to consume; required for unused +linear bindings |optional; produces an Affine-echo residue (see §6) + +|Branches in `+T_Case+`, `+T_If+` |must agree on `+(R', G')+` exactly +|may differ; meet operation on outputs (also a thin-poset operation) +|=== + +`+T_Region+`, `+T_Region_Active+`, `+T_Loc+`, `+T_StringNew+`, +`+T_StringConcat+`, `+T_Pair+`, `+T_App+`, etc. are +*modality-polymorphic* — the rule shape is identical in both modes. + +==== Proof obligations specific to each mode + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Property |Ephapax-Linear |Ephapax-Affine +|Preservation |✓ (L1 fix) |✓ (same fix; Affine derivations are L1-safe +by weakening) + +|Progress |✓ |✓ + +|*No-leak* (every introduced linear value is consumed) |proved |does +*not* hold; replaced by "`no-duplicate`" + +|*No-duplicate* |trivially (Linear ⇒ no-duplicate) |proved as a +structural property + +|*Resource-exact* (linear count = linear introductions) |proved |not +stated + +|*Garbage residue inhabited* |not applicable |proved (every silent drop +has a residue trace) +|=== + +Cross-mode: the Linear ⇒ Affine weakening lemma is a single induction. +Combined with monomode preservation, this gives Affine preservation for +free. + +''''' + +=== 6. Layer 3 — Echo / residue, in design + +This layer is *not required for preservation*. Documented here so that +L1 + L2 don’t bake in assumptions that block L3 later. + +==== 6.1 Echo, the fiber + +Echo-types defines: + +[source,agda] +---- +Echo : (A → B) → B → Set +Echo f y = Σ A (λ x → f x ≡ y) +---- + +An echo of `+y+` under `+f+` is a witnessed preimage — proof-relevant +because _which_ `+x+` mapped to `+y+` is information the irreversibility +of `+f+` deliberately erased. (`+Echo.agda:14-15+`.) + +For ephapax, the irreversible operations are: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Operation |Collapse |Echo type +|`+S_Region_Exit+` of `+r+` +|`+collapse_region_r : LiveAt_r → ExitedAt_r+` +|`+Echo collapse_region_r exited+` — witnessed by which value escaped + +|`+S_Drop+` of `+v : T+` |`+collapse_drop : T → ⊤+` +|`+Echo collapse_drop tt+` — full fiber on `+T+` + +|Implicit drop (Affine only) |as above, but residue is `+EchoR+` +(lowered) |`+EchoR ⊤ TrivialCert tt+` +|=== + +==== 6.2 Two modes, one type former + +Following `+EchoLinear.agda:30-58+`: + +.... +LEcho : Mode → Set +LEcho Linear = Echo collapse tt -- full fiber, mandatory observation +LEcho Affine = EchoR ⊤ TrivialCert tt -- lowered residue, optional +.... + +*Linear echo is not a different type from affine echo.* It is the same +fiber, with a different observation discipline imposed by the modality +layer. The weakening `+weaken : LEcho Linear → LEcho Affine+` _is_ the +echo-lowering map (`+EchoResidue.agda:33-73+`). This is what the user +means by "`echo semantics and structural discipline must remain +orthogonal and compositional`": L2 chooses the mode; L3 is the same +fiber regardless. + +==== 6.3 Where echo enters the typing rules + +The L3 extension introduces: + +* A new type former `+TEcho (op : irreversible_op) : ty+`. +* An operational rule pairing: `+S_Region_Exit+` and `+S_Drop+` produce +a residue *value* of type `+TEcho ⟨op⟩+`. In Linear mode this value must +be threaded into a `+T_Observe+` somewhere; in Affine mode it may be +implicitly dropped (which itself produces a `+TEcho ⟨affine- drop⟩+`, +but that is observed automatically by the runtime). +* The bookkeeping is hierarchical-via-fibration; no coherence +obligations arise because each level is a thin poset (decoration +commuting holds — `+EchoLinear.agda:93-101+`). + +==== 6.4 What L3 demands that L1+L2 must not contradict + +To keep L3 viable as a future extension, the L1 and L2 design must: + +[arabic] +. *Not bake "`irreversible step ⇒ no residue`" into preservation.* The +current `+S_Region_Exit+` has `+expr_free_of_region r v+` as a premise +but no residue value. The L3 extension _adds_ the residue value. +Preservation as restated in §4.5 must not assume residue = nothing. +. *Not introduce per-type "`echo-ability`" predicates.* Echo is a +property of operations, not of types. Echo-types’ canonical formulation +(fiber-of-a-collapse) keeps the type former minimal. +. *Not assume mode-monomorphic typing rules.* The L1/L2 rules are +modality-polymorphic; L3’s `+T_Observe+` is the one rule that splits per +mode (Linear-`+T_Observe+` consumes; Affine-`+T_Observe+` doesn’t). + +§4 and §5 as drafted satisfy all three. + +''''' + +=== 7. Layer 4 — Dyadic interaction (the mother–child distinction) + +The user’s note — + +____ +Linear Ephapax is the exact-consumption / obligation-preserving regime +and is the true home of strict dyadic semantics. Affine Ephapax permits +weakening and graceful abandonment, so its dyadicity is +relaxed/degradable rather than fully obligation- symmetric. +____ + +— frames Linear and Affine ephapax not as "`strict vs lax`" but as the +*asymmetric pair* of a dyadic interaction: Linear is the obligation- +bearer (the _speaker_ of the dyad), Affine is the obligation-relaxer +(the _listener_). Both must coexist because real programs require both +sides of the interaction. + +This dyadicity is itself orthogonal to L1, L2, L3: + +* L1 (region capabilities) is the _same_ in both Linear and Affine — +both must track region exit precisely for soundness. +* L2 (modality) gives the _direction_ of the dyad — which side is +obligation-bearing. +* L3 (echo) is the _protocol_ of obligation discharge — what the +observer must do with the residue. + +L4 is *not a separate proof layer*. It is a labelling discipline at the +program / module level: a closed program is declared ephapax-Linear or +ephapax-Affine (or a designated module-boundary mix); the corresponding +judgment-mode (L2) is selected, and L3 follows from that. No proofs +change. + +''''' + +=== 8. Proof-need separation: Linear vs Affine + +Where the layers’ proof needs differ: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Property |Ephapax-Linear |Ephapax-Affine +|*Preservation* (L1) |identical statement; proof via L1 threading +|identical statement; follows from Linear via modality-weakening + +|*Progress* |values + closed contexts step |values + closed contexts +step; affine "`ambient drop`" rule may fire + +|*Resource exactness* |`+count_intro = count_consume+` for linear-typed +values |replaced by `+count_consume ≤ count_intro+` + +|*Sound region exit* (L1 corollary) |no derivation types a sibling +reference to an exited region |same + +|*No-leak / leak-bounded* |no-leak strictly |leak-bounded (residue +accumulated as Affine-echo) + +|*No-duplicate* |trivial corollary |structural induction on absence of +`+T_Copy+` for linear types + +|*Echo observation closure* (L3) |every irreversible step’s residue is +observed by program end |every residue is observed-or-dropped; +non-duplication holds + +|*Mode coherence* (L4) |a Linear program does not invoke Affine-mode +rules |an Affine program may call Linear-typed functions via mode +embedding +|=== + +The two sublanguages share *preservation and progress*. They differ on +resource counting and on echo observation. L3 is where the proof needs +genuinely split. + +''''' + +=== 9. Counterexample examination, revisited + +The verified counterexample (`+Counterexample.v+`) was a _single_ +witness. The L1 architectural fix subsumes a family of related +configurations, all sharing the same shape. Cataloguing them sharpens +the design: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Family |Generator |Variant of counterexample +|*F1 — sibling re-references exited region* +|`+EPair (ERegion r v) (e[r])+` with `+r ∈ free_regions(type(e[r]))+` +|the proved counterexample + +|*F2 — let-body references exited region* |`+ELet (ERegion r v) (e[r])+` +where the bound variable is unused but `+r+` appears |same shape via +`+T_Let+` + +|*F3 — function-application sibling* |`+EApp (ERegion r f) (ELoc _ r)+` +|`+T_App+`, same shape + +|*F4 — string-concat sibling* +|`+EStringConcat (ERegion r e1) (ELoc _ r)+` |`+T_StringConcat+`, +blocked by L1 because both children must type at the _same_ +`+TString r+`; second child is `+ELoc _ r+` requiring `+r ∈ R_mid+` + +|*F5 — case-branch references exited region* +|`+ECase (ERegion r e) (ELoc _ r) (ELoc _ r)+` — both branches reference +`+r+` |`+T_Case+`, blocked by L1 because branches must agree on +`+R_out+`, and the scrutinee’s `+R_out+` no longer contains `+r+` + +|*F6 — nested same-name region* +|`+ERegion r (EPair (ERegion r v) (ELoc _ r))+` |distinguishes +`+T_Region+` from `+T_Region_Active+`; under L1 both exits pop one `+r+` +and the inner sibling is typed at the right shrunken `+R+` +|=== + +All six families derive from the same defect: sibling typings under a +sibling-shared `+R+`. L1’s threading eliminates the entire family in one +structural change. + +A family L1 does *not* address: sibling reference to a region +_introduced_ (not exited) by a sibling — e.g., +`+EPair (ERegion r v) (ELoc _ r)+` where the second sibling expects +`+r+` to be active. This is already disallowed by `+T_Region+`’s +`+~ In r R+` premise (the fresh `+r+` is local to the body, not visible +to the sibling). Verified separately that the second sibling would +require `+r ∈ R_in+`, but `+r ∉ R_in+` by `+T_Region+`’s premise — so +this is well-typedness, not preservation, and the existing rules already +reject it. + +This confirms the diagnosis: *the asymmetry is region _exit_, not region +introduction*. L1’s left-to-right threading is the exactly- sufficient +mechanism. + +''''' + +=== 10. Implementation order + +[arabic] +. *L1 — region threading* (this design). Closes preservation. Days, not +weeks. The hard part is re-stating ~80 lemmas; the substance of each is +unchanged. +. *L2 — modality parameter* (already present _in_ the types via +`+is_linear_ty+`; needs to be _promoted_ to a judgment parameter plus +the Linear ⇒ Affine weakening). Orthogonal to L1; can be done before or +after. +. *L3 — echo type former* (forward-looking; not required for +preservation, progress, or the dyadic story). Bring in echo-types’ +`+Echo+`, `+EchoR+`, and `+EchoLinear+` mode poset verbatim; layer them +via decoration-commuting. +. *L4 — module-level mode declaration* (UX, not proof). + +Each step’s correctness is independent of the next. The decoration- +commuting recipe from echo-types (`+composition.md+` §Q5) guarantees +that adding L3 to a system that already has L1+L2 does not invalidate +any L1/L2 proof. + +''''' + +=== 11. References + +* The verified counterexample: `+formal/Counterexample.v+` (this repo). +* The diagnostic record of attempted patches: +`+formal/PRESERVATION-HANDOFF.md+`. +* Echo definition: +`+~/developer/repos/echo-types/proofs/agda/Echo.agda+` (line 14). +* Echo residue + non-recovery: +`+~/developer/repos/echo-types/proofs/agda/EchoResidue.agda+` (lines +16–66). +* Linear/Affine mode poset: +`+~/developer/repos/echo-types/proofs/agda/EchoLinear.agda+` (lines +30–101). +* Decoration commuting / orthogonality recipe: +`+~/developer/repos/echo-types/proofs/agda/EchoGraded.agda+` (lines +73–86, 141–158) and +`+~/developer/repos/echo-types/docs/echo-types/composition.md+` (§Q5). +* Disambiguation: `+CLAUDE.md+` (this repo) for ephapax-vs-AffineScript +boundary; ephapax-linear and ephapax-affine are _internal sublanguages_ +of one project, not separate projects. + +''''' + +=== 12. Documentation rollout — making the revised story legible + +The L1–L4 redesign changes how Ephapax _is described_, not just how it +is implemented. The current documentation talks about "`linear+affine +type system`" as if the dyad were the whole story; under the new +architecture it is *one of four orthogonal layers*, and the more +interesting story is the layering itself. This section catalogues the +documents that frame "`what ephapax is`" and specifies the substantive +edits required, with draft text where the framing matters. + +The rollout deliberately leaves implementation untouched. Documentation +that promises the new architecture without it being built would be +dishonest; documentation that describes the *direction* alongside the +current state is a roadmap. The text below is the roadmap form. + +==== 12.1 Tagline and one-line description (used by many surfaces) + +The current GitHub repo description is: + +____ +Dyadic linear+affine type system for compile-time WASM memory safety — +no use-after-free, no leaks, region-based allocation. Mechanically +proved in Coq and Idris2. +____ + +Proposed replacement (matches the four-layer story without overclaiming +unbuilt layers): + +____ +A dyadic programming language for WebAssembly, where four orthogonal +disciplines — structural (linear ↔ affine), region capabilities, +irreversibility residue, and dyadic interaction mode — compose to +guarantee compile-time memory safety without a garbage collector. +Mechanically formalised in Coq and Idris2. +____ + +Short form (≤ 130 chars, for cards and `+site:+` listings): + +____ +Ephapax: four-layer dyadic type system for WASM memory safety — +linearity, regions, echo residue, dyadic mode. Coq + Idris2 proofs. +____ + +Lift this string verbatim into: + +* GitHub repo description +(`+gh repo edit hyperpolymath/ephapax --description …+`). +* `+site/index.md+` hero subtitle. +* `+.well-known/funding.json+` / any project-listing metadata. +* The Pages site `+_config+` (if any) and the GH wiki landing page. + +==== 12.2 README.adoc — repo front door + +`+README.adoc+` (lines 43–96) is currently structured as "`what this is +(table)`" + "`what this isn’t`" + "`Hello, world`". The table maps +closely onto L1–L4 once the framing is named. *Targeted edits, not a +rewrite:* + +[arabic] +. *Add a new section after `+== What this is+`* titled +`+== The four layers+`, listing L1–L4 in the same `+[cols="1,3"]+` table +style: ++ +[source,adoc] +---- +== The four layers + +Ephapax composes four orthogonal disciplines. Each is a thin-poset +refinement, so they compose without coherence obligations +(https://github.com/hyperpolymath/echo-types[echo-types] supplies +the recipe). + +[cols="1,3"] +|=== +| Layer | What it enforces + +| **L1 — Region capabilities** +| Every live region is tracked in an input/output environment + threaded through every expression. A region cannot be referenced + after a sibling has exited it. Soundness proof in `formal/`. + +| **L2 — Structural discipline (linear ↔ affine)** +| The *modality* of the surrounding program decides whether linear + bindings must be consumed (Linear: ephapax-linear) or may be + dropped (Affine: ephapax-affine). Same syntax, same semantics — + different admissible derivations. Linear ⊆ Affine. + +| **L3 — Irreversibility residue (Echo)** +| Operations that erase information (region exit, drop) produce + proof-relevant residue — `Echo f y := Σ A (λ x → f x ≡ y)`. In + Linear mode the residue must be observed; in Affine mode it + may be silently lowered. Forward-looking; not yet in the typing + rules. + +| **L4 — Dyadic interaction mode** +| A project-level declaration of which side of the dyad the + program speaks from. Selects the L2 modality and the L3 + observation discipline. No proof obligations of its own. +|=== +---- +. *Update the "`Dyadic discipline`" row of the existing table* to +cross-reference the new section: ++ +____ +*Dyadic discipline* | This is *L2* (see "`The four layers`" below). Each +binding is _either_ affine (`+let+` — used at most once, weakening +allowed) _or_ linear (`+let!+` — used exactly once, weakening +forbidden). Type checker enforces. +____ +. *Replace "`Region-based memory`" row* with the L1 framing: ++ +____ +*Region-based memory (L1)* | Allocations live in named regions +(`+region r: ...+`). The type system threads a region-capability +environment through every expression so a sibling cannot read from a +region another sibling has just exited. When a region’s scope ends, the +runtime bulk-frees every resource in it. +____ +. *Add a footnote-style block* linking to this design document for +readers who want the soundness story: ++ +[source,adoc] +---- +.Soundness story +**** +The four-layer separation is **not** decorative — it exists +because the original "linear+affine + regions" framing admitted a +verified counterexample to preservation (see +`formal/Counterexample.v` and `formal/PRESERVATION-DESIGN.md`). +The four-layer redesign restores soundness by making each +discipline's invariants explicit and orthogonal. +**** +---- +. *Do not* change the disambiguation block (lines 19–41). It is correct +and load-bearing. + +==== 12.3 EXPLAINME.adoc — receipts + +`+EXPLAINME.adoc+` is the "`show me the evidence`" companion to the +README. Today it backs up the dyadic + two-AST + pattern-matching + +dual-grammar + region claims. Under the new architecture it must back up +two new claims and weaken one: + +* *New (L1)*: "`The region capability environment is threaded through +every compound expression.`" Evidence pointer: cite +`+formal/PRESERVATION-DESIGN.md §4+` and `+formal/Counterexample.v+` +(until the Coq side is implemented, the evidence is the *design doc + +counterexample*, not a passing proof — say so honestly). +* *New (L3, planned)*: "`Irreversible operations produce residue +witnesses.`" Evidence pointer: `+formal/PRESERVATION-DESIGN.md §6+` and +the upstream echo-types proofs at +`+~/developer/repos/echo-types/proofs/agda/EchoLinear.agda+`. Mark as +*planned* until ephapax has its own `+formal/Echo.v+`. +* *Weaken*: the existing "`Dyadic Type System`" claim should now point +to `+EPHAPAX-VISION.adoc+` and to the L2 description in the README, +rather than presenting L2 in isolation. + +Add a new top-level section after "`Claim: Region-Based Memory`": + +[source,adoc] +---- +== Claim: Sibling-safe region capabilities (L1) + +*Evidence*: Region capabilities are threaded as input/output +environments through every compound typing rule. A sibling cannot +reference a region a previous sibling has exited. + +* Counterexample at `formal/Counterexample.v` (all three lemmas + `Qed`) demonstrates the soundness gap the threading fixes. +* Design rationale at `formal/PRESERVATION-DESIGN.md §3-§4`. +* Reference implementation: forthcoming — see `ROADMAP.adoc` for + the order in which L1 lands in `formal/` and in + `src/ephapax-typing/`. + +== Claim: Irreversibility residue is first-class (L3, planned) + +*Evidence (planned)*: Operations that erase information will produce +typed residue witnesses, following the +https://github.com/hyperpolymath/echo-types[echo-types] +formulation. Linear mode requires the residue to be observed; +Affine mode permits silent lowering. + +* Upstream theory at + `~/developer/repos/echo-types/proofs/agda/EchoLinear.agda` (lines + 30–101), already mechanised. +* Forward-looking design at `formal/PRESERVATION-DESIGN.md §6`. +* Status: **planned** — no ephapax rule yet introduces `TEcho`. The + L3 extension follows L1. +---- + +==== 12.4 docs/vision/EPHAPAX-VISION.adoc — the design vision + +The vision doc already articulates the dyad beautifully (mother / child, +"`ephapax means once for all`" double-meaning, "`Linear ⊂ Affine in +valid programs`", "`one language, one feel`"). The four-layer story +_extends_ this; it does not replace it. + +*Insert a new section between the existing `+== The Dyad+` and +`+== One Language, One Feel+`*: + +[source,adoc] +---- +== The Dyad and the Layers + +The dyad — linear mother, affine child — is one of *four orthogonal +disciplines* that together define Ephapax. The other three are +silent partners in the dyad: each is enforced independently, but +each takes its **defaults** from which side of the dyad you are on. + +[cols="1,3", options="header"] +|=== +| Layer | What the dyad says about it + +| L1 — Region capabilities +| Same in both. The mother and the child both forbid use-after-exit; + the dyadic mode does not relax this. (This is the layer whose + *absence* admitted the verified counterexample to preservation.) + +| L2 — Structural discipline +| The dyad *is* this layer. Linear is the mother; Affine is the + child. Linear derivations are a subset of Affine derivations. + +| L3 — Irreversibility residue +| The mother demands the residue be *observed*. The child permits + it to be *lowered* — present but trivial. The same residue value, + two observation disciplines. + +| L4 — Dyadic interaction +| The declaration of which side you speak from. The mother answers + to the linear discipline; the child to the affine. Every closed + program has one. +|=== + +The four layers compose without coherence obligations because each +is a *thin poset* — propositional ordering kills the categorical +overhead that would otherwise demand pentagon laws and the like. The +construction is taken verbatim from +https://github.com/hyperpolymath/echo-types[echo-types]' +decoration-commuting recipe. + +The dyad remains primary. The layers are how the dyad's promises +are *enforced*. +---- + +The existing prose ("`one mother, one child, one dyad`", "`the child is +not deficient`", "`as Ephapax matures, the affine form must mature into +itself`") is unchanged. The layer story extends it; it doesn’t replace +it. + +==== 12.5 ROADMAP.adoc — sequencing + +The current ROADMAP carries the *preservation closure plan* at the top. +The redesign supersedes that plan. New top section: + +[source,adoc] +---- +== Four-layer redesign (2026-05-26 → ) + +The verified preservation counterexample +(`formal/Counterexample.v`) closed the original closure plan as +unreachable in the current rules. The new plan separates four +orthogonal layers; each is implemented in sequence. + +. **L1 — Region capability threading** + - Restate `has_type` to thread `R_in / R_out`. + - Update every typing rule + every supporting lemma in + `Semantics.v`, `Typing.v`. + - Reprove preservation against `Counterexample.v` as a regression. + - Land as one PR per cluster (typing-rule changes, semantics + re-statement, lemma migration, preservation reproof). +. **L2 — Modality parameter** + - Promote `is_linear_ty` from a per-type predicate to a judgment + parameter `ℓ ∈ {Linear, Affine}`. + - Implement `weaken_modality : Linear → Affine` as a single + induction. + - Restate the existing dual-grammar story (in `ephapax-linear/`) + in terms of the modality. +. **L3 — Echo residue** + - Add `formal/Echo.v` mirroring `echo-types/proofs/agda/Echo.agda`. + - Introduce `TEcho ⟨op⟩` and the `T_Observe` rule. + - Modify `S_Region_Exit` and `S_Drop` to produce residue values. +. **L4 — Mode declaration** + - Project-level metadata (`Cargo.toml` or `ephapax.toml`). + - Type checker reads the declaration and selects the L2 modality. + - No proof obligations. + +The previous "Preservation closure plan" section is **archived** — +see `formal/PRESERVATION-HANDOFF.md` for the historical record. +---- + +==== 12.6 spec/SPEC.md and spec/ephapax-spec.md — the canonical spec + +The spec is the binding contract for implementers. It must: + +* Define `+R_in / R_out+` threading on the typing judgment. +* State the L1 sibling-safety invariant as a theorem. +* Mark L3 (Echo) as "`future extension; not normative`". +* Reference `+EPHAPAX-VISION.adoc+` for the dyad framing. + +Add an introduction paragraph: + +____ +Ephapax’s type system has four orthogonal disciplines: *L1* region +capabilities, *L2* structural modality (linear/affine), *L3* +irreversibility residue (Echo Types — planned), and *L4* dyadic +interaction mode (project-level declaration). This specification is +normative for L1 and L2 and forward-looking for L3 and L4. +____ + +==== 12.7 ephapax-linear/README.md — sublanguage docs + +`+ephapax-linear/+` hosts both the linear and affine checkers (the crate +name predates the dyadic naming). Its README currently compares the two +grammars. Add a banner at the top: + +[source,markdown] +---- +> **Naming note.** This crate is called `ephapax-linear` for +> historical reasons; it implements **both** L2 modalities +> (Linear and Affine). The two are not different languages — they +> are two admissible-derivation regimes over the same syntax and +> semantics. See `docs/vision/EPHAPAX-VISION.adoc` for the dyad, +> and `formal/PRESERVATION-DESIGN.md §5` for the L2 layer. +---- + +Update the comparison table title from "`Dual Substructural Grammars`" +to "`Two L2 Modalities`". + +==== 12.8 CLAUDE.md — agent guidance + +CLAUDE.md already has the AffineScript disambiguation (correct, keep). +Add a section after the disambiguation block: + +[source,markdown] +---- +## The four orthogonal layers + +When working in this repo, the typing system has four layers. Knowing +which layer a question touches is the first step in answering it: + +| Layer | One-line question to ask | +|---|---| +| L1 — Region capabilities | "Does this involve `R_in`, `R_out`, or `In r R`?" | +| L2 — Structural modality | "Is this about consumption, weakening, or Linear vs Affine?" | +| L3 — Echo residue | "Is this about *what was lost* when an irreversible step fired?" | +| L4 — Dyadic mode | "Is this a project-level mode declaration question?" | + +Most questions touch exactly one layer. The design rationale is in +`formal/PRESERVATION-DESIGN.md`. The verified counterexample that +forced the redesign is in `formal/Counterexample.v`. + +Standing rule: if a proposed change appears to require a side +condition on a compound typing rule (e.g. "the sibling doesn't +reference this region"), pause — the four-layer threading should make +that side condition *derivable*, not stated. +---- + +==== 12.9 CHANGELOG.md — the next entry + +[source,markdown] +---- +## [Unreleased] + +### Architecture + +- **Four-layer typing redesign** (`formal/PRESERVATION-DESIGN.md`). + The original "linear+affine + regions" framing admitted a verified + counterexample to preservation (`formal/Counterexample.v`). The + redesign separates four orthogonal disciplines: region capabilities + (L1), structural modality (L2), irreversibility residue (L3, + planned), and dyadic interaction mode (L4). Each is a thin-poset + decoration, composing without coherence obligations. + +### Proof / theory + +- Verified counterexample to preservation in the current rules + (`formal/Counterexample.v` — three lemmas `Qed`). The + counterexample is the canonical regression test for the L1 fix. + +### Docs + +- New design document: `formal/PRESERVATION-DESIGN.md`. +- README, EXPLAINME, EPHAPAX-VISION, ROADMAP, CLAUDE updated to + reflect the four-layer story. +- Repo description and tagline updated. +---- + +==== 12.10 site/index.md and the GH Pages homepage + +Wherever the homepage exists (`+site/index.md+` is the current +candidate; the repo also has +`+homepage = https://hyperpolymath.github.io/ephapax/+`), update the +hero block: + +[source,markdown] +---- +# Ephapax + +> Four-layer dyadic type system for WebAssembly memory safety. + +Four orthogonal disciplines compose to guarantee compile-time +memory safety without a garbage collector: + +- **Regions** (L1) — capabilities threaded through every expression. +- **Linear ↔ Affine** (L2) — the dyad, mother and child. +- **Echo** (L3, planned) — irreversibility leaves typed residue. +- **Dyadic mode** (L4) — declare which side you speak from. + +Mechanically formalised in Coq and Idris2. See +[design](formal/PRESERVATION-DESIGN.md) · +[vision](docs/vision/EPHAPAX-VISION.adoc) · +[spec](spec/SPEC.md) · +[roadmap](ROADMAP.adoc). +---- + +==== 12.11 TOPOLOGY.md — the map + +TOPOLOGY.md describes the repo’s directory layout. Add a column mapping +each top-level dir to which layer(s) it implements: + +[source,markdown] +---- +| Path | Purpose | Layer(s) | +|---|---|---| +| `src/ephapax-typing/` | type checker | L1, L2 | +| `src/ephapax-syntax/`, `src/ephapax-surface/` | AST | L1, L2 (same syntax) | +| `ephapax-linear/` | dual grammars + checkers | L2 | +| `formal/` | Coq mechanisation | L1 (L2 + L3 planned) | +| `idris2/`, `src/abi/Ephapax/` | Idris2 mechanisation | L1 | +| `docs/vision/`, `EPHAPAX-VISION.adoc` | dyad framing | L4 | +| `spec/` | normative spec | L1, L2 | +| (future) `formal/Echo.v` | residue mechanisation | L3 | +---- + +==== 12.12 .machine_readable/6a2/*.a2ml — the structured metadata + +These files (`+STATE.a2ml+`, `+META.a2ml+`, `+ECOSYSTEM.a2ml+`, +`+AGENTIC.a2ml+`, `+NEUROSYM.a2ml+`, `+PLAYBOOK.a2ml+`) feed downstream +tooling (hypatia, k9, the cartridge story). Each needs a structural +update: + +[width="100%",cols="50%,50%",options="header",] +|=== +|File |Update +|`+STATE.a2ml+` |Add `+@architecture: four-layer-typing-redesign+` +block; mark L1 in-flight, L2 partial-existing, L3 planned, L4 planned. + +|`+META.a2ml+` |Add `+@layers:+` array listing L1–L4 with one-line +descriptions. Cross-reference echo-types as upstream theory dependency. + +|`+ECOSYSTEM.a2ml+` |Add `+@relates_to: echo-types+` (theory) and update +`+@guarantees:+` to name L1 + L2 invariants. + +|`+AGENTIC.a2ml+` |Add a disambiguation block: "`L1/L2/L3/L4 are layer +names; ephapax-linear/ephapax-affine are L2 modes; Linear/Affine in +echo-types are L3 modes — names overlap deliberately because the +underlying poset is the same; agents must disambiguate by context.`" + +|`+NEUROSYM.a2ml+` |If it carries proof-state vectors, update +preservation status: `+closed-with-redesign-planned+` (was +`+open-12-admits+`). + +|`+PLAYBOOK.a2ml+` |Add an entry: "`when a proof attempt requires +sibling region disjointness as a side condition, escalate to L1 redesign +rather than patching.`" +|=== + +==== 12.13 GitHub wiki + +`+gh repo view+` confirms wiki is enabled (`+hasWikiEnabled: true+`) but +the wiki has no content surveyed in this session. Recommended page +structure (clone +`+git clone git@github.com:hyperpolymath/ephapax.wiki.git+` locally, or +create via the wiki UI): + +[width="100%",cols="50%,50%",options="header",] +|=== +|Page |Content +|`+Home+` |Hero + four-layer summary + links into the repo (vision, +design, spec, roadmap). + +|`+The-Four-Layers+` |Long-form explainer of L1–L4, with diagrams. Same +content as the new README §"`The four layers`" but expanded with +motivating examples. + +|`+The-Dyad+` |Excerpt + adapt from `+EPHAPAX-VISION.adoc+`’s "`The +Dyad`" and "`One Language, One Feel`" sections. Public-facing. + +|`+Region-Capabilities+` |Long-form L1 explainer with the counterexample +walked through (this is the *selling point* for the redesign — make it +visceral). + +|`+Echo-Types-and-Residue+` |L3 explainer, citing echo-types as +upstream. Mark as "`planned`" prominently. + +|`+Dyadic-Mode-Declaration+` |L4 page; mode declarations, when each +fits. + +|`+Soundness+` |Index of what is proved, where, and at what status. +Cross-references `+formal/PRESERVATION-DESIGN.md+` and +`+Counterexample.v+`. + +|`+FAQ+` |Includes the disambiguation against AffineScript, the +relationship to echo-types, why four layers (not three or five), and why +the redesign was forced rather than chosen. + +|`+Glossary+` |Terms: region capability, modality, echo, residue, dyadic +mode, thin-poset decoration. +|=== + +Wiki updates should happen *after* the repo-side docs land, so the wiki +can link to merged sources rather than to in-flight files. + +==== 12.14 Cross-cutting: language for new framing (drop-ins) + +These passages are written once and dropped wherever needed: + +*One-sentence what-this-is*: > Ephapax is a dyadic programming language +whose type system composes > four orthogonal disciplines — region +capabilities, structural > modality, irreversibility residue, and dyadic +mode — to guarantee > compile-time WebAssembly memory safety without a +garbage collector. + +*One-paragraph what-this-is*: > Ephapax is a programming language for +WebAssembly with a > mechanically verified type system. Four orthogonal +disciplines > compose to give the safety guarantees: *region +capabilities* (L1) > thread an explicit live-region set through every +expression so a > sibling cannot reference a region another sibling has +exited; > *structural modality* (L2) chooses between Linear (every +linear > binding must be consumed) and Affine (linear bindings may be > +dropped); *irreversibility residue* (L3, planned) makes the > +information lost by region exit and drop into a first-class, > +proof-relevant value; and *dyadic mode* (L4) declares, at the > project +level, which side of the linear/affine dyad the program > speaks from. +The four layers compose without coherence obligations > because each is +a thin-poset decoration, a construction borrowed > verbatim from the +`+echo-types+` mechanisation. + +*Why four layers (FAQ-shaped)*: > Q: Why four layers, not three or five? +> > A: The four are what the soundness story demands. L1 is forced by > +a verified counterexample to preservation: sibling-safe region > exit +cannot be enforced without explicit capability threading. L2 > already +exists in the implementation as the linear/affine split; > the redesign +promotes it from a per-type to a per-judgment notion > and proves it +Linear ⊆ Affine. L3 is required because Linear mode > demands that +irreversibility be _witnessed_, not silently > performed; echo-types +supplies the formal machinery. L4 is the > project-level dial that +selects which way L2 and L3 default. Drop > any one and the architecture +has a hole; add a fifth and you > duplicate an existing layer. + +*Why not just patch preservation (FAQ)*: > Q: Why redesign the type +system rather than patch the proof? > > A: `+formal/Counterexample.v+` +is `+Qed+`: it exhibits a well-typed > input that single-steps to an +untypable output, at the same outer > type. That is preservation +_failing_, not preservation > _unproven_. The fix isn’t a tactic; it’s a +missing invariant. We > add the invariant explicitly (L1 threading) +rather than via ad-hoc > side conditions on every compound rule, because +sibling-region- > disjointness should be a _theorem_ about the new +threading, not a > premise on every rule. + +==== 12.15 Proof debt: what is proved today, what is *not*, and what this redesign will / will not change + +This subsection is required reading before any future claim about +"`ephapax preservation`" or "`ephapax soundness`" is made — by a human +or by an agent. The architecture above describes the *target*; this +subsection describes the *current state*, which is much narrower than +the target. + +===== 12.15.1 What is mechanised today + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Mechanisation |Scope |Status +|`+formal/Semantics.v+`, `+formal/Typing.v+`, `+formal/Syntax.v+` (Coq) +|A *single* typing judgment `+R; G ⊢ e : T -| G'+` and its small-step +operational semantics |Builds with `+coqc 8.18.0+` + +|`+formal/Counterexample.v+` |A verified counterexample to preservation +*as currently stated* |All three lemmas `+Qed+` + +|`+preservation+` theorem in `+Semantics.v+` |Soundness under the +current single judgment |*`+Admitted.+`* — 11 cascading goals open (see +`+PRESERVATION-HANDOFF.md+`) + +|`+src/abi/Ephapax/…/*.idr+` (Idris2) |Selected structural-safety claims +|Per file; see `+idris2 --check+` +|=== + +===== 12.15.2 What the single judgment *is not* + +The current `+has_type+` judgment *does not distinguish* Linear from +Affine ephapax. It is a single typing relation, parameterised over a +linearity context `+G+` whose entries carry a `+(ty, bool)+` consumption +flag. `+is_linear_ty+` returns a per-type tag. There is *no judgment +parameter* for L2 modality; there is *no proof* that Linear derivations +form a subset of Affine derivations; there is *no mechanised +ephapax-affine type system* at all. + +Concretely: the Rust crate `+ephapax-linear/+` contains a +`+LinearChecker+` and an `+AffineChecker+`, with EBNF grammars and +behavioural tests proving they diverge on identical programs. That is +the implementation side of the dyad. The *proofs side* has not caught +up: the Coq + Idris2 formalisation models _one_ judgment, which +behaviourally resembles the linear discipline (it requires linear-typed +bindings to be consumed) but is not labelled as such and has no proven +dual. + +===== 12.15.3 What this redesign *will* establish + +If executed as designed, the redesign closes: + +* *L1 — region capabilities*: a new threading discipline + a reproof of +preservation under the new rules. This is the central deliverable. The +verified counterexample becomes a passing regression. +* *L2 promotion*: the judgment gains an `+ℓ ∈ {Linear, Affine}+` +parameter, and a `+weaken_modality : Linear ⇒ Affine+` lemma. This is +what _introduces_ a mechanised ephapax-affine into the formal +development for the first time. + +===== 12.15.4 What this redesign *will not* establish + +Equally explicitly — so future sessions, agents, and PR reviewers do not +assume otherwise: + +* *The ephapax-affine type proofs are not done today, and the L1 +redesign on its own does not finish them.* L1 closes preservation under +the _single_ judgment that already exists. L2’s modality-weakening +lemma, once proved, will lift L1’s preservation to the affine side — but +that lift is a _separate_ lemma that has not been written and is not +part of the L1 patch. Until L2 lands _and_ the weakening lemma is +`+Qed+`, *no ephapax-affine soundness property is mechanised*. +* *No L3 (Echo / residue) proof obligation is closed.* L3 is +forward-looking. Until `+formal/Echo.v+` exists and references it from +the operational semantics, the Linear-echo "`mandatory observation`" +property and the Affine-echo "`non-duplicable residue`" property are +_aspirations_, not theorems. +* *No proof that the Rust `+AffineChecker+` agrees with any mechanised +affine judgment.* The implementation-side dual exists; the +mechanisation-side dual does not. Cross-checking the two is a third, +independent body of work. +* *No proof of the L4 mode declaration’s consistency* (e.g., that a +program declared Linear cannot accidentally invoke an Affine- typed +library through a hole in mode-elaboration). L4 is a UX layer in this +design; making it sound requires further work that is not scoped here. + +===== 12.15.5 The honest one-line summary + +____ +*Ephapax’s mechanised soundness story today is: one judgment, resembling +linear discipline, with preservation `+Admitted+`. The verified +counterexample shows preservation false as stated. The L1 redesign in +this document closes that counterexample. Ephapax- affine proofs as a +distinct mechanised body of theorems are not done, will not be done by +L1 alone, and will require L2 plus a follow-up weakening lemma before +any "`ephapax-affine is sound`" claim is honest.* +____ + +Cite this subsection by section number (`+§12.15+`) whenever the +question of "`what is proved`" is asked. Do not paraphrase the bounds +upward. + +==== 12.16 Future research track — "`Echo as foundation`" (v2) + +The four-layer design in this document treats *Echo as L3*: a residue +layer over the existing operational primitives (region exit, drop). This +is interpretation *B* of the question "`can Echo replace the reclamation +model in ephapax?`" — Echo _augments_ the existing region model; it does +not replace it. + +Interpretation *A* is a coherent research direction, but a much larger +one: making Echo the *operational foundation* itself. In an A-shaped +calculus: + +* `+ERelease v+` becomes a primitive operation: yields a residue value +of type `+TEcho ⟨op⟩+` _and_ releases the underlying memory. +* `+region r { … }+` becomes *syntactic sugar* for a sequence of +releases at scope exit, in reverse-allocation order. +* The operational semantics’ primitive step is the release event, not +the region exit. +* Linear-mode discharge becomes a *termination invariant*: a closed +Linear program with no outstanding echo obligations is provably +leak-free. +* Surface-language UX research is required: implicit-at-scope-exit vs +explicit `+release x+` vs both. Borrow semantics under deferred release +is a real theory question, not a notation question. + +*This is a sibling v2 calculus, not a patch.* It will require its own +Coq development (`+formal/Semantics_v2.v+` or a separate +`+ephapax-echo-foundation/+` crate), its own design doc +(`+formal/ECHO-AS-FOUNDATION.md+`, currently not written), and its own +roadmap. The migration story (current ephapax programs ↦ v2 calculus) is +itself a research question. + +*Why this is staged separately from B:* + +[arabic] +. *B closes preservation in weeks; A is months.* Preservation is +currently `+Admitted+` with a verified counterexample. Closing it is +more urgent than v2 architectural exploration. +. *B and A are not mutually exclusive.* Once B lands, A can be developed +as a parallel branch using B’s primitives. The L3 echo layer in B is a +_prerequisite_ for A’s foundation rewrite — nothing wasted. +. *Echo-types itself follows this restraint.* Its formal contribution is +editorial reframing of homotopy fibers as irreversibility residues, not +a new operational foundation. The same restraint applies here: +Echo-as-L3 is the addition; Echo-as- foundation is a v2 design that +earns its keep through experimentation, not by decree. + +*Concrete next steps for A* (when undertaken; not now): + +[width="100%",cols="50%,50%",options="header",] +|=== +|Step |Output +|1. Design doc |`+formal/ECHO-AS-FOUNDATION.md+` — primitives, +semantics, type rules + +|2. Borrow-under-release theory |Resolve how borrows extend across +release points; mechanise + +|3. Coq prototype |`+formal/Semantics_v2.v+` with `+ERelease+` / +`+EObserve+` primitives + +|4. Linear discharge as termination invariant |Stronger than B’s +Linear-discharge theorem + +|5. Region desugaring equivalence |Prove `+region r { e }+` ≡ sequenced +releases of e’s allocations + +|6. Surface-syntax UX research |Implicit-vs-explicit release; +default-scope semantics; ergonomic patterns + +|7. WasmGC interop boundary |Explicit boundary types between +echo-tracked and GC-tracked values + +|8. Migration plan |Map v1 ephapax programs ↦ v2; most code should be +syntactic-sugar-preserved +|=== + +*Tracking*: when the time comes, file as an issue +`+ephapax: research — Echo as operational foundation (v2 calculus)+` +with this section as the seed. Roadmap entry below. + +Add to `+ROADMAP.adoc+` under "`Future research tracks`": + +[source,adoc] +---- +=== Echo as operational foundation (v2) + +The four-layer redesign (`formal/PRESERVATION-DESIGN.md`) treats Echo +as a residue layer (L3) over regions. A coherent v2 research direction +makes Echo the *foundation* itself: `ERelease` as a primitive +operation, regions desugared to sequenced releases, Linear-mode +discharge as a termination invariant. + +**Not scheduled.** This is a months-long research effort, staged +after the L1+L2+L3 layer-redesign lands. See +`formal/PRESERVATION-DESIGN.md §12.16` for the seed; an +`ECHO-AS-FOUNDATION.md` design doc will be opened when the work +begins. +---- + +==== 12.17 Execution order + +The user has not asked for these edits to be made yet. Execution order, +when they are: + +[arabic] +. Land `+formal/PRESERVATION-DESIGN.md+` (this document). +. Update README + EXPLAINME + EPHAPAX-VISION in one PR ("`docs: +introduce four-layer framing`"). +. Update ROADMAP + CHANGELOG in one PR ("`docs: roadmap reflects +four-layer redesign; counterexample as regression`"). +. Update CLAUDE + .machine_readable a2ml in one PR ("`docs(agent): +layer-aware guidance`"). +. Update spec/ in one PR ("`spec: introduce L1 R-threading; L3 +forward-looking`"). +. Update site/index.md + repo description + tagline in one PR ("`site: +four-layer hero`"). +. Wiki updates last, out-of-band (wiki commits don’t go through PR +review). + +All in-repo PRs follow standing policy: GPG-signed, auto-merge ON, GH +conventions matched. diff --git a/formal/PRESERVATION-DESIGN.md b/formal/PRESERVATION-DESIGN.md deleted file mode 100644 index 4f67b963..00000000 --- a/formal/PRESERVATION-DESIGN.md +++ /dev/null @@ -1,1267 +0,0 @@ - - - - -# Preservation: principled redesign - -Companion to `PRESERVATION-HANDOFF.md`. The handoff document is a -diagnostic record of attempted proof-engineering. This document is the -**design** rationale for the typing-layer change that the verified -counterexample (`Counterexample.v`, all three lemmas `Qed.`) now makes -unavoidable. - -The handoff's Option 3 ("type-system change") is here re-cast not as a -patch but as the deliberate separation of four concerns that the -current calculus conflates: **structural discipline**, -**region capability tracking**, **dyadic interaction semantics**, and -**echo / residue semantics**. Preservation is derived from explicit -invariants in the new architecture; it is not forced through the old. - ---- - -## 1. What the counterexample proves - -`formal/Counterexample.v` exhibits a configuration where the calculus -admits a single-step reduction whose result is *untypable* at the same -outer type — i.e. preservation as stated is **false**, not unproven. - -| Input | Type at `R_in = [r0; r1]` | -|---|---| -| `EPair (ERegion r1 (ELoc l0 r0)) (ELoc l1 r1)` | `TProd (TString r0) (TString r1)` | - -After one `S_Pair_Step1` (lifting `S_Region_Exit` on the first child): - -| Output | Type at `R' = [r0]` | -|---|---| -| `EPair (ELoc l0 r0) (ELoc l1 r1)` | **none** (sibling `ELoc l1 r1` requires `In r1 [r0]`, false) | - -### The missing invariant, named - -The sibling `ELoc l1 r1` was typed under the assumption that `r1` -would be live. The first child *invalidated that assumption* by -exiting `r1` mid-evaluation. The typing judgment has no place to -record that the input region environment for the second sibling -depends on the output region environment of the first. - -> **Missing invariant — region capability monotonicity per -> sub-expression**: for any compound form `C(e₁, …, eₙ)`, the -> capability environment in which `e_{i+1}` is typed equals the -> capability environment *left over* after `e_i` evaluates. - -The current rules thread the *linearity* context `G` left-to-right -through compound rules, but the *region* environment `R` is shared -statically by all siblings. That asymmetry is the bug. - ---- - -## 2. Why a side-condition patch is wrong - -The handoff lists three minimal-patch candidates: mutual induction, -inversion-on-`Hstep` structural recursion (~150 LOC helper), or an -ad-hoc sibling-region-disjointness side condition on `T_Pair`, `T_Let`, -`T_App`, …. - -All three preserve the architectural defect (R as a static, sibling- -shared parameter) and force the proof through it. The disjointness -side condition is the worst of the three because it scatters the -invariant across every compound rule, making future rules (Echo Types, -effect/capability extensions, dyadic interaction primitives) responsible -for re-discovering and re-imposing the same constraint at every -introduction. - -The principled fix is to thread R the same way G is threaded. The -sibling-region-disjointness *property* then **follows as a corollary** -of the new threading; it is no longer a rule premise. - ---- - -## 3. The four orthogonal concerns - -The redesign separates four discipline layers that the current calculus -braids together: - -| Layer | Concern | What it tracks | Encoded as | -|---|---|---|---| -| **L1** | Region capabilities | Which regions are currently live; how they shrink under reduction | Input/output capability sets `R_in → R_out` threaded through every rule | -| **L2** | Structural discipline | Whether linear bindings must be consumed (Linear) or may be implicitly dropped (Affine) | Modality parameter `ℓ ∈ {Linear, Affine}` on the judgment + a thin-poset weakening `Linear ⇒ Affine` | -| **L3** | Echo / residue | Whether irreversible operations (region exit, drop) produce a residue witness that must (Linear-echo) or may (Affine-echo) be observed | Echo types `Echo f y := Σ A (λ x → f x ≡ y)` ([echo-types/Echo.agda:14](https://github.com/hyperpolymath/echo-types/blob/main/proofs/agda/Echo.agda#L14)); residue obligation tracked by a third thin-poset decoration | -| **L4** | Dyadic interaction | The mother–child pairing of the language: ephapax-linear vs ephapax-affine as observable interaction modes | Top-level mode declaration; consequences cascade into L2 and L3 defaults | - -The key claim — borrowed verbatim from the echo-types calculus -(`EchoLinear.agda:30-101`) — is that **decoration commuting** holds: -because each layer's order is a thin poset (subset on `R`, the -two-point modality `Linear ≤ Affine`, the residue refinement -`mandatory ≤ optional`), composition of layers is definitional. No -coherence burden is introduced; the layers do not have to "agree" on -anything beyond their independent invariants. - -This is the architectural payoff: **adding L3 later does not require -reproving anything about L1 and L2.** - ---- - -## 4. Layer 1 in detail — the preservation fix - -### 4.1 Judgment signature - -Current: - -``` -R ; G ⊢ e : T -| G' -``` - -New: - -``` -R ; G ⊢ e : T -| R' ; G' -``` - -`R'` is the capability environment **after** `e` has reduced to a -value. It is determined syntactically from `e` (see §4.3); the change -is syntax-directed, not inferential. - -### 4.2 Compound rules thread `R` left-to-right - -Every compound rule that currently threads `G` left-to-right gains the -corresponding `R` threading. Example, `T_Pair`: - -```coq -| T_Pair : forall R R' R'' G G' G'' e1 e2 T1 T2, - R ; G ⊢ e1 : T1 -| R' ; G' -> - R' ; G' ⊢ e2 : T2 -| R'' ; G'' -> - R ; G ⊢ EPair e1 e2 : TProd T1 T2 -| R'' ; G'' -``` - -Same shape change for `T_Let`, `T_LetLin`, `T_App`, `T_StringConcat`, -`T_If`, `T_Case` (with branches required to *agree* on `R_out`), -`T_Fst`, `T_Snd`, `T_Inl`, `T_Inr`, `T_Drop`, `T_Copy`, `T_Borrow_Val`. - -### 4.3 Region rules expose the capability shift - -`T_Loc` and value rules: `R_out = R_in` (values don't affect -capabilities). - -`T_Region` (fresh region introduction; `~ In r R_in`): - -```coq -| T_Region : forall R R_body G G' r e T, - ~ In r R -> - ~ In r (free_regions T) -> - (r :: R) ; G ⊢ e : T -| R_body ; G' -> - R ; G ⊢ ERegion r e : T -| remove_first r R_body ; G' -``` - -`T_Region_Active` (re-entering an already-live region; `In r R_in`): - -```coq -| T_Region_Active : forall R G G' r e T, - In r R -> - ~ In r (free_regions T) -> - R ; G ⊢ e : T -| R ; G' -> (* body must NOT exit r *) - R ; G ⊢ ERegion r e : T -| remove_first r R ; G' -``` - -The two regional rules now make the capability cost of the construct -**explicit** at the typing level. `S_Region_Exit`'s operational effect -(shrink `R` by one `r`) is mirrored by the typing rule's `R_out`. - -### 4.4 The counterexample no longer types - -Under the new rules: - -- `ERegion r1 (ELoc l0 r0)` typed at `R_in = [r0; r1]`: - by `T_Region_Active` with `In r1 [r0; r1]` ✓ and - `~ In r1 (free_regions (TString r0)) = ~ In r1 [r0]` ✓. - Body `ELoc l0 r0`: `R_in = [r0; r1]`, `R_out = [r0; r1]`. - Outer `R_out = remove_first r1 [r0; r1] = [r0]`. - -- Now `EPair`'s second sibling `ELoc l1 r1` must type at - `R = [r0]`. `T_Loc` requires `region_active [r0] r1`, false. - -- **The `EPair` rule has no derivation.** Counterexample disappears. - -### 4.5 Preservation under the new judgment - -The original goal — - -> If `R; G ⊢ e : T -| G'` and `(μ, R, e) → (μ', R', e')` then -> `R'; G ⊢ e' : T -| G'`. - -— now becomes: - -> If `R; G ⊢ e : T -| R_final; G'` and `(μ, R, e) → (μ', R', e')` then -> there exists `R'_final` such that `R'; G ⊢ e' : T -| R'_final; G'` and -> `R'_final` is consistent with `R_final` (specifically: `R_final` is -> reachable from `R'_final` by zero or more applications of the -> remaining region exits in `e'`). - -The 11 admits on the `touches_region` RIGHT branch dissolve: each -admit was asking "how do I re-type the unchanged sibling under the -shrunken R'?". Under the new threading the sibling is typed at -`R'` from the outset — there is nothing to re-type. - -### 4.6 Cost: which existing lemmas need re-proof - -The judgment shape changes, so every lemma that pattern-matches the -old shape (`R; G |- e : T -| G'`) must be updated. The lemma count -in the current `Semantics.v` is ~80; most are mechanical signature -updates. The **substance** of each proof is unchanged because the -new R-threading is just a parallel copy of the existing G-threading. - -A targeted estimate (the user explicitly de-prioritises patch size, -so this is informational only): - -| Lemma class | Count (approx) | Substance change | -|---|---:|---| -| `value_context_unchanged` and variants | ~6 | none — values: `R_out = R_in` | -| `subst_preserves_typing`, `_strong` | 2 | thread `R` like `G` | -| `region_shrink_preserves_typing`, `_dup` | 2 | restated; some may become redundant | -| `region_add_typing`, `region_env_perm_typing` | 2 | restated | -| `step_R_eq_or_touches_region`, `step_R_change_shape` | 2 | possibly subsumed by the new judgment | -| `step_preserves_type`, `step_output_context_eq` and at-pre helpers | 4 | restate `step_output_context_eq` to relate `R_final` and `R'_final`; `step_preserves_type` proves the new preservation statement directly | -| `preservation` | 1 | proved by structural induction on the new judgment | - -The at-pre helper pattern survives because it is orthogonal to the -threading change: the pre-step env is the new `R_in` of the -diagonal typing rule. - -### 4.7 L1 closure status (2026-05-27) - -Six follow-up PRs against `proof/l1-region-threading-design` (the -design branch) landed the L1 implementation incrementally: - -| PR | Scope | Outcome | -|---|---|---| -| #155 | `TypingL1.v` — the R-threaded judgment + counterexample regression | `bad_input_untypable_l1` Qed | -| #157 | `Semantics_L1.v` skeleton — `preservation_l1` stated | scaffold | -| #158 (L1.A) | `value_R_G_preserving_l1` | Qed | -| #159 (L1.B) | `region_shrink_preserves_typing_l1` (routes via `_gen`) | Qed; `_gen` Admitted (2 internal admits at the time) | -| #160 (L1.C) | `subst_preserves_typing_l1` (strengthened statement) | Qed; introduced a sub-Axiom for the inner-R retype obligation | -| #161 (L1.D) | `preservation_l1` proof body | 29/33 cases Qed; 4 admits surface region-env weakening gap | -| #162 (L1.F) | Discharge unsound `loc_retype_at_R_l1` axiom | Original Axiom replaced by a Qed-able Lemma + a narrower (still admitted) `region_liveness_at_split_l1`; 2 of 11 call sites discharge directly | -| #163 (L1.E) | Region-env weakening attack on the 6 admits (2 in `_gen` + 4 in `preservation_l1`) | 2 admits closed via new `count_occ_le_l1` monotonicity lemma; 4 admits remain with structural diagnosis (see below) | - -After all six PRs land on the design branch: - -- **4 admits remain** in `Semantics_L1.v`: - 1. `region_shrink_preserves_typing_l1_gen` line ~390 — `T_Region_Active_L1` shadowed case. List-vs-multiset mismatch: set-equality bridges don't preserve `remove_first_L1` outputs (the operation depends on list position, not just membership). - 2. `preservation_l1` `S_StringConcat_Step2` — operationally sound; needs a `step_pop_disjoint_from_type_l1` lemma (tractable, deferred to L1.G). - 3. `preservation_l1` `S_App_Step2` — see §4.8 (fundamental soundness gap). - 4. `preservation_l1` `S_Pair_Step2` — see §4.8 (same gap). - -- **1 Axiom remains**: `region_liveness_at_split_l1` (a narrower, sound-as-stated statement of what the original unsound `loc_retype_at_R_l1` was reaching for). Three closure paths documented in-file: strengthen `T_Var_Lin_L1`, add a side condition to `subst_typing_gen_l1`, or carry `In rv R_intermediate` through the induction. - -- **2 `Admitted.` markers** remain (`region_shrink_preserves_typing_l1_gen` and `preservation_l1`) — each closes the moment its internal admits close. - -### 4.8 Soundness finding: T_Lam_L1 + T_Var_*_L1 are too permissive - -L1.E surfaced a genuine soundness issue, separate from the proof- -engineering gaps: - -**The finding.** `T_Lam_L1` rigidly fixes the lambda body's region -environment `R` at lambda-creation time, with no mechanism to retype -the body at a shifted `R` before application. Combined with the -orthogonal observation that `T_Var_Lin_L1` and `T_Var_Unr_L1` permit -typing variables of type `TString r` at *any* `R` (including `R = []`), -the L1 typing system is genuinely too permissive about region presence -for preservation under `R`-shifting steps. - -**Why this matters operationally.** `S_App_Step2` evaluates the -argument of `EApp v_fn e_arg` when `v_fn` is a value (a lambda). The -inner step on `e_arg` may shift `R → R'` via a region operation. The -lambda's typing was fixed at the *original* `R`; the rules give no -way to re-derive its typing at `R'`. The preservation theorem is -therefore unprovable for these cases under the current rules — not a -proof-engineering gap but a calculus-design gap. - -**Resolution paths** (each independent): - -1. **Effect-typed lambdas (L2/L3 territory)**: parameterise function - types over the region-effect they have, so the typing rule sees - how the body changes `R` and can re-derive at the call site. -2. **Restrict T_Lam_L1**: require the lambda body to be region-pure - (no free regions in `T1`, `T2`, or the body's intermediate types). - This is restrictive but unblocks preservation immediately. -3. **Restrict T_Var_*_L1**: require `In r R` for any variable whose - type contains `r`. This propagates the region constraint into the - variable-use side, closing the gap. - -The L1.E PR documents this in-source. None of (1)/(2)/(3) is in scope -for the L1 minimal-fix; each is a follow-up. See ROADMAP for sequencing. - ---- - -## 5. Layer 2 in detail — Linear vs Affine modality - -Currently `is_linear_ty` and `T_Drop`/`T_Copy` encode a per-*type* -distinction (a `TRef Lin` is linear, a `TRef Unr` is not). The user -clarified that there is a stronger, **language-level** dyadicity: - -- **Ephapax-Linear**: strict exact-consumption semantics. Weakening - forbidden. Drop must be explicit and consumes the value. Every - irreversible operation must produce an observed residue. -- **Ephapax-Affine**: permits implicit drop. Weakening allowed. - Irreversible operations may silently produce a non-duplicable - residue trace. - -Encode the modality as a judgment parameter: - -``` -R ; G ⊢_ℓ e : T -| R' ; G' where ℓ ∈ {Linear, Affine} -``` - -The modality lives on the **judgment**, not on the type. The two -sublanguages share the same syntax (`Syntax.v` unchanged) and the -same operational semantics (`Semantics.v` unchanged); they differ in -which derivations the typing relation admits. - -### Linear ⇒ Affine: a thin-poset decoration - -Every Linear derivation is an Affine derivation (Linear is the more -restrictive mode). This is the modality weakening: - -```coq -weaken_modality : forall R G e T R' G', - R ; G ⊢_Linear e : T -| R' ; G' -> - R ; G ⊢_Affine e : T -| R' ; G' -``` - -This mirrors `EchoLinear.agda:53-58` (`weaken : LEcho linear → LEcho -affine`). Because `Linear ≤ Affine` is propositional, the weakening -commutes with R-threading and G-threading by the decoration-commuting -recipe (echo-types' `degradeMode-comp`, `EchoLinear.agda:93-101`). - -### What changes per mode - -| Rule | Linear | Affine | -|---|---|---| -| `T_Lam` | `(T1, true) :: G` on body output | `(T1, true_or_unused) :: G` | -| Top-level (closed terms) | `G = G' = []` required | `G = []`, `G'` may carry unused linear bindings | -| `T_Drop` | discharges an *obligation* to consume; required for unused linear bindings | optional; produces an Affine-echo residue (see §6) | -| Branches in `T_Case`, `T_If` | must agree on `(R', G')` exactly | may differ; meet operation on outputs (also a thin-poset operation) | - -`T_Region`, `T_Region_Active`, `T_Loc`, `T_StringNew`, -`T_StringConcat`, `T_Pair`, `T_App`, etc. are **modality-polymorphic** -— the rule shape is identical in both modes. - -### Proof obligations specific to each mode - -| Property | Ephapax-Linear | Ephapax-Affine | -|---|---|---| -| Preservation | ✓ (L1 fix) | ✓ (same fix; Affine derivations are L1-safe by weakening) | -| Progress | ✓ | ✓ | -| **No-leak** (every introduced linear value is consumed) | proved | does **not** hold; replaced by "no-duplicate" | -| **No-duplicate** | trivially (Linear ⇒ no-duplicate) | proved as a structural property | -| **Resource-exact** (linear count = linear introductions) | proved | not stated | -| **Garbage residue inhabited** | not applicable | proved (every silent drop has a residue trace) | - -Cross-mode: the Linear ⇒ Affine weakening lemma is a single induction. -Combined with monomode preservation, this gives Affine preservation -for free. - ---- - -## 6. Layer 3 — Echo / residue, in design - -This layer is **not required for preservation**. Documented here so -that L1 + L2 don't bake in assumptions that block L3 later. - -### 6.1 Echo, the fiber - -Echo-types defines: - -```agda -Echo : (A → B) → B → Set -Echo f y = Σ A (λ x → f x ≡ y) -``` - -An echo of `y` under `f` is a witnessed preimage — proof-relevant -because *which* `x` mapped to `y` is information the irreversibility -of `f` deliberately erased. (`Echo.agda:14-15`.) - -For ephapax, the irreversible operations are: - -| Operation | Collapse | Echo type | -|---|---|---| -| `S_Region_Exit` of `r` | `collapse_region_r : LiveAt_r → ExitedAt_r` | `Echo collapse_region_r exited` — witnessed by which value escaped | -| `S_Drop` of `v : T` | `collapse_drop : T → ⊤` | `Echo collapse_drop tt` — full fiber on `T` | -| Implicit drop (Affine only) | as above, but residue is `EchoR` (lowered) | `EchoR ⊤ TrivialCert tt` | - -### 6.2 Two modes, one type former - -Following `EchoLinear.agda:30-58`: - -``` -LEcho : Mode → Set -LEcho Linear = Echo collapse tt -- full fiber, mandatory observation -LEcho Affine = EchoR ⊤ TrivialCert tt -- lowered residue, optional -``` - -**Linear echo is not a different type from affine echo.** It is the -same fiber, with a different observation discipline imposed by the -modality layer. The weakening `weaken : LEcho Linear → LEcho Affine` -*is* the echo-lowering map (`EchoResidue.agda:33-73`). This is what -the user means by "echo semantics and structural discipline must -remain orthogonal and compositional": L2 chooses the mode; L3 is the -same fiber regardless. - -### 6.3 Where echo enters the typing rules - -The L3 extension introduces: - -- A new type former `TEcho (op : irreversible_op) : ty`. -- An operational rule pairing: `S_Region_Exit` and `S_Drop` produce a - residue **value** of type `TEcho ⟨op⟩`. In Linear mode this value - must be threaded into a `T_Observe` somewhere; in Affine mode it may - be implicitly dropped (which itself produces a `TEcho ⟨affine- - drop⟩`, but that is observed automatically by the runtime). -- The bookkeeping is hierarchical-via-fibration; no coherence - obligations arise because each level is a thin poset (decoration - commuting holds — `EchoLinear.agda:93-101`). - -### 6.4 What L3 demands that L1+L2 must not contradict - -To keep L3 viable as a future extension, the L1 and L2 design must: - -1. **Not bake "irreversible step ⇒ no residue" into preservation.** - The current `S_Region_Exit` has `expr_free_of_region r v` as a - premise but no residue value. The L3 extension *adds* the residue - value. Preservation as restated in §4.5 must not assume residue = - nothing. -2. **Not introduce per-type "echo-ability" predicates.** Echo is a - property of operations, not of types. Echo-types' canonical - formulation (fiber-of-a-collapse) keeps the type former minimal. -3. **Not assume mode-monomorphic typing rules.** The L1/L2 rules are - modality-polymorphic; L3's `T_Observe` is the one rule that splits - per mode (Linear-`T_Observe` consumes; Affine-`T_Observe` doesn't). - -§4 and §5 as drafted satisfy all three. - ---- - -## 7. Layer 4 — Dyadic interaction (the mother–child distinction) - -The user's note — - -> Linear Ephapax is the exact-consumption / obligation-preserving -> regime and is the true home of strict dyadic semantics. Affine -> Ephapax permits weakening and graceful abandonment, so its -> dyadicity is relaxed/degradable rather than fully obligation- -> symmetric. - -— frames Linear and Affine ephapax not as "strict vs lax" but as the -**asymmetric pair** of a dyadic interaction: Linear is the obligation- -bearer (the *speaker* of the dyad), Affine is the obligation-relaxer -(the *listener*). Both must coexist because real programs require both -sides of the interaction. - -This dyadicity is itself orthogonal to L1, L2, L3: - -- L1 (region capabilities) is the *same* in both Linear and Affine — - both must track region exit precisely for soundness. -- L2 (modality) gives the *direction* of the dyad — which side is - obligation-bearing. -- L3 (echo) is the *protocol* of obligation discharge — what the - observer must do with the residue. - -L4 is **not a separate proof layer**. It is a labelling discipline at -the program / module level: a closed program is declared -ephapax-Linear or ephapax-Affine (or a designated module-boundary mix); -the corresponding judgment-mode (L2) is selected, and L3 follows from -that. No proofs change. - ---- - -## 8. Proof-need separation: Linear vs Affine - -Where the layers' proof needs differ: - -| Property | Ephapax-Linear | Ephapax-Affine | -|---|---|---| -| **Preservation** (L1) | identical statement; proof via L1 threading | identical statement; follows from Linear via modality-weakening | -| **Progress** | values + closed contexts step | values + closed contexts step; affine "ambient drop" rule may fire | -| **Resource exactness** | `count_intro = count_consume` for linear-typed values | replaced by `count_consume ≤ count_intro` | -| **Sound region exit** (L1 corollary) | no derivation types a sibling reference to an exited region | same | -| **No-leak / leak-bounded** | no-leak strictly | leak-bounded (residue accumulated as Affine-echo) | -| **No-duplicate** | trivial corollary | structural induction on absence of `T_Copy` for linear types | -| **Echo observation closure** (L3) | every irreversible step's residue is observed by program end | every residue is observed-or-dropped; non-duplication holds | -| **Mode coherence** (L4) | a Linear program does not invoke Affine-mode rules | an Affine program may call Linear-typed functions via mode embedding | - -The two sublanguages share **preservation and progress**. They differ -on resource counting and on echo observation. L3 is where the proof -needs genuinely split. - ---- - -## 9. Counterexample examination, revisited - -The verified counterexample (`Counterexample.v`) was a *single* -witness. The L1 architectural fix subsumes a family of related -configurations, all sharing the same shape. Cataloguing them -sharpens the design: - -| Family | Generator | Variant of counterexample | -|---|---|---| -| **F1 — sibling re-references exited region** | `EPair (ERegion r v) (e[r])` with `r ∈ free_regions(type(e[r]))` | the proved counterexample | -| **F2 — let-body references exited region** | `ELet (ERegion r v) (e[r])` where the bound variable is unused but `r` appears | same shape via `T_Let` | -| **F3 — function-application sibling** | `EApp (ERegion r f) (ELoc _ r)` | `T_App`, same shape | -| **F4 — string-concat sibling** | `EStringConcat (ERegion r e1) (ELoc _ r)` | `T_StringConcat`, blocked by L1 because both children must type at the *same* `TString r`; second child is `ELoc _ r` requiring `r ∈ R_mid` | -| **F5 — case-branch references exited region** | `ECase (ERegion r e) (ELoc _ r) (ELoc _ r)` — both branches reference `r` | `T_Case`, blocked by L1 because branches must agree on `R_out`, and the scrutinee's `R_out` no longer contains `r` | -| **F6 — nested same-name region** | `ERegion r (EPair (ERegion r v) (ELoc _ r))` | distinguishes `T_Region` from `T_Region_Active`; under L1 both exits pop one `r` and the inner sibling is typed at the right shrunken `R` | - -All six families derive from the same defect: sibling typings under a -sibling-shared `R`. L1's threading eliminates the entire family in one -structural change. - -A family L1 does **not** address: sibling reference to a region -*introduced* (not exited) by a sibling — e.g., -`EPair (ERegion r v) (ELoc _ r)` where the second sibling expects `r` -to be active. This is already disallowed by `T_Region`'s -`~ In r R` premise (the fresh `r` is local to the body, not visible -to the sibling). Verified separately that the second sibling would -require `r ∈ R_in`, but `r ∉ R_in` by `T_Region`'s premise — so this -is well-typedness, not preservation, and the existing rules already -reject it. - -This confirms the diagnosis: **the asymmetry is region *exit*, not -region introduction**. L1's left-to-right threading is the exactly- -sufficient mechanism. - ---- - -## 10. Implementation order - -1. **L1 — region threading** (this design). Closes preservation. - Days, not weeks. The hard part is re-stating ~80 lemmas; the - substance of each is unchanged. -2. **L2 — modality parameter** (already present *in* the types via - `is_linear_ty`; needs to be *promoted* to a judgment parameter - plus the Linear ⇒ Affine weakening). Orthogonal to L1; can be - done before or after. -3. **L3 — echo type former** (forward-looking; not required for - preservation, progress, or the dyadic story). Bring in - echo-types' `Echo`, `EchoR`, and `EchoLinear` mode poset - verbatim; layer them via decoration-commuting. -4. **L4 — module-level mode declaration** (UX, not proof). - -Each step's correctness is independent of the next. The decoration- -commuting recipe from echo-types (`composition.md` §Q5) guarantees -that adding L3 to a system that already has L1+L2 does not invalidate -any L1/L2 proof. - ---- - -## 11. References - -- The verified counterexample: `formal/Counterexample.v` (this repo). -- The diagnostic record of attempted patches: `formal/PRESERVATION-HANDOFF.md`. -- Echo definition: `~/developer/repos/echo-types/proofs/agda/Echo.agda` (line 14). -- Echo residue + non-recovery: `~/developer/repos/echo-types/proofs/agda/EchoResidue.agda` (lines 16–66). -- Linear/Affine mode poset: `~/developer/repos/echo-types/proofs/agda/EchoLinear.agda` (lines 30–101). -- Decoration commuting / orthogonality recipe: `~/developer/repos/echo-types/proofs/agda/EchoGraded.agda` (lines 73–86, 141–158) and `~/developer/repos/echo-types/docs/echo-types/composition.md` (§Q5). -- Disambiguation: `CLAUDE.md` (this repo) for ephapax-vs-AffineScript boundary; ephapax-linear and ephapax-affine are *internal sublanguages* of one project, not separate projects. - ---- - -## 12. Documentation rollout — making the revised story legible - -The L1–L4 redesign changes how Ephapax *is described*, not just how it -is implemented. The current documentation talks about "linear+affine -type system" as if the dyad were the whole story; under the new -architecture it is **one of four orthogonal layers**, and the more -interesting story is the layering itself. This section catalogues the -documents that frame "what ephapax is" and specifies the substantive -edits required, with draft text where the framing matters. - -The rollout deliberately leaves implementation untouched. Documentation -that promises the new architecture without it being built would be -dishonest; documentation that describes the **direction** alongside -the current state is a roadmap. The text below is the roadmap form. - -### 12.1 Tagline and one-line description (used by many surfaces) - -The current GitHub repo description is: - -> Dyadic linear+affine type system for compile-time WASM memory safety -> — no use-after-free, no leaks, region-based allocation. Mechanically -> proved in Coq and Idris2. - -Proposed replacement (matches the four-layer story without overclaiming -unbuilt layers): - -> A dyadic programming language for WebAssembly, where four orthogonal -> disciplines — structural (linear ↔ affine), region capabilities, -> irreversibility residue, and dyadic interaction mode — compose to -> guarantee compile-time memory safety without a garbage collector. -> Mechanically formalised in Coq and Idris2. - -Short form (≤ 130 chars, for cards and `site:` listings): - -> Ephapax: four-layer dyadic type system for WASM memory safety — -> linearity, regions, echo residue, dyadic mode. Coq + Idris2 proofs. - -Lift this string verbatim into: - -- GitHub repo description (`gh repo edit hyperpolymath/ephapax --description …`). -- `site/index.md` hero subtitle. -- `.well-known/funding.json` / any project-listing metadata. -- The Pages site `_config` (if any) and the GH wiki landing page. - -### 12.2 README.adoc — repo front door - -`README.adoc` (lines 43–96) is currently structured as "what this is -(table)" + "what this isn't" + "Hello, world". The table maps closely -onto L1–L4 once the framing is named. **Targeted edits, not a -rewrite:** - -1. **Add a new section after `== What this is`** titled - `== The four layers`, listing L1–L4 in the same `[cols="1,3"]` table - style: - - ```adoc - == The four layers - - Ephapax composes four orthogonal disciplines. Each is a thin-poset - refinement, so they compose without coherence obligations - (https://github.com/hyperpolymath/echo-types[echo-types] supplies - the recipe). - - [cols="1,3"] - |=== - | Layer | What it enforces - - | **L1 — Region capabilities** - | Every live region is tracked in an input/output environment - threaded through every expression. A region cannot be referenced - after a sibling has exited it. Soundness proof in `formal/`. - - | **L2 — Structural discipline (linear ↔ affine)** - | The *modality* of the surrounding program decides whether linear - bindings must be consumed (Linear: ephapax-linear) or may be - dropped (Affine: ephapax-affine). Same syntax, same semantics — - different admissible derivations. Linear ⊆ Affine. - - | **L3 — Irreversibility residue (Echo)** - | Operations that erase information (region exit, drop) produce - proof-relevant residue — `Echo f y := Σ A (λ x → f x ≡ y)`. In - Linear mode the residue must be observed; in Affine mode it - may be silently lowered. Forward-looking; not yet in the typing - rules. - - | **L4 — Dyadic interaction mode** - | A project-level declaration of which side of the dyad the - program speaks from. Selects the L2 modality and the L3 - observation discipline. No proof obligations of its own. - |=== - ``` - -2. **Update the "Dyadic discipline" row of the existing table** to - cross-reference the new section: - - > **Dyadic discipline** | This is **L2** (see "The four layers" - > below). Each binding is *either* affine (`let` — used at most - > once, weakening allowed) *or* linear (`let!` — used exactly once, - > weakening forbidden). Type checker enforces. - -3. **Replace "Region-based memory" row** with the L1 framing: - - > **Region-based memory (L1)** | Allocations live in named regions - > (`region r: ...`). The type system threads a region-capability - > environment through every expression so a sibling cannot read - > from a region another sibling has just exited. When a region's - > scope ends, the runtime bulk-frees every resource in it. - -4. **Add a footnote-style block** linking to this design document - for readers who want the soundness story: - - ```adoc - .Soundness story - **** - The four-layer separation is **not** decorative — it exists - because the original "linear+affine + regions" framing admitted a - verified counterexample to preservation (see - `formal/Counterexample.v` and `formal/PRESERVATION-DESIGN.md`). - The four-layer redesign restores soundness by making each - discipline's invariants explicit and orthogonal. - **** - ``` - -5. **Do not** change the disambiguation block (lines 19–41). It is - correct and load-bearing. - -### 12.3 EXPLAINME.adoc — receipts - -`EXPLAINME.adoc` is the "show me the evidence" companion to the -README. Today it backs up the dyadic + two-AST + pattern-matching + -dual-grammar + region claims. Under the new architecture it must -back up two new claims and weaken one: - -- **New (L1)**: "The region capability environment is threaded - through every compound expression." Evidence pointer: cite - `formal/PRESERVATION-DESIGN.md §4` and `formal/Counterexample.v` - (until the Coq side is implemented, the evidence is the **design - doc + counterexample**, not a passing proof — say so honestly). -- **New (L3, planned)**: "Irreversible operations produce residue - witnesses." Evidence pointer: `formal/PRESERVATION-DESIGN.md §6` - and the upstream echo-types proofs at - `~/developer/repos/echo-types/proofs/agda/EchoLinear.agda`. Mark as - **planned** until ephapax has its own `formal/Echo.v`. -- **Weaken**: the existing "Dyadic Type System" claim should now - point to `EPHAPAX-VISION.adoc` and to the L2 description in the - README, rather than presenting L2 in isolation. - -Add a new top-level section after "Claim: Region-Based Memory": - -```adoc -== Claim: Sibling-safe region capabilities (L1) - -*Evidence*: Region capabilities are threaded as input/output -environments through every compound typing rule. A sibling cannot -reference a region a previous sibling has exited. - -* Counterexample at `formal/Counterexample.v` (all three lemmas - `Qed`) demonstrates the soundness gap the threading fixes. -* Design rationale at `formal/PRESERVATION-DESIGN.md §3-§4`. -* Reference implementation: forthcoming — see `ROADMAP.adoc` for - the order in which L1 lands in `formal/` and in - `src/ephapax-typing/`. - -== Claim: Irreversibility residue is first-class (L3, planned) - -*Evidence (planned)*: Operations that erase information will produce -typed residue witnesses, following the -https://github.com/hyperpolymath/echo-types[echo-types] -formulation. Linear mode requires the residue to be observed; -Affine mode permits silent lowering. - -* Upstream theory at - `~/developer/repos/echo-types/proofs/agda/EchoLinear.agda` (lines - 30–101), already mechanised. -* Forward-looking design at `formal/PRESERVATION-DESIGN.md §6`. -* Status: **planned** — no ephapax rule yet introduces `TEcho`. The - L3 extension follows L1. -``` - -### 12.4 docs/vision/EPHAPAX-VISION.adoc — the design vision - -The vision doc already articulates the dyad beautifully (mother / -child, "ephapax means once for all" double-meaning, "Linear ⊂ Affine -in valid programs", "one language, one feel"). The four-layer story -*extends* this; it does not replace it. - -**Insert a new section between the existing `== The Dyad` and -`== One Language, One Feel`**: - -```adoc -== The Dyad and the Layers - -The dyad — linear mother, affine child — is one of *four orthogonal -disciplines* that together define Ephapax. The other three are -silent partners in the dyad: each is enforced independently, but -each takes its **defaults** from which side of the dyad you are on. - -[cols="1,3", options="header"] -|=== -| Layer | What the dyad says about it - -| L1 — Region capabilities -| Same in both. The mother and the child both forbid use-after-exit; - the dyadic mode does not relax this. (This is the layer whose - *absence* admitted the verified counterexample to preservation.) - -| L2 — Structural discipline -| The dyad *is* this layer. Linear is the mother; Affine is the - child. Linear derivations are a subset of Affine derivations. - -| L3 — Irreversibility residue -| The mother demands the residue be *observed*. The child permits - it to be *lowered* — present but trivial. The same residue value, - two observation disciplines. - -| L4 — Dyadic interaction -| The declaration of which side you speak from. The mother answers - to the linear discipline; the child to the affine. Every closed - program has one. -|=== - -The four layers compose without coherence obligations because each -is a *thin poset* — propositional ordering kills the categorical -overhead that would otherwise demand pentagon laws and the like. The -construction is taken verbatim from -https://github.com/hyperpolymath/echo-types[echo-types]' -decoration-commuting recipe. - -The dyad remains primary. The layers are how the dyad's promises -are *enforced*. -``` - -The existing prose ("one mother, one child, one dyad", "the child is -not deficient", "as Ephapax matures, the affine form must mature into -itself") is unchanged. The layer story extends it; it doesn't replace -it. - -### 12.5 ROADMAP.adoc — sequencing - -The current ROADMAP carries the **preservation closure plan** at the -top. The redesign supersedes that plan. New top section: - -```adoc -== Four-layer redesign (2026-05-26 → ) - -The verified preservation counterexample -(`formal/Counterexample.v`) closed the original closure plan as -unreachable in the current rules. The new plan separates four -orthogonal layers; each is implemented in sequence. - -. **L1 — Region capability threading** - - Restate `has_type` to thread `R_in / R_out`. - - Update every typing rule + every supporting lemma in - `Semantics.v`, `Typing.v`. - - Reprove preservation against `Counterexample.v` as a regression. - - Land as one PR per cluster (typing-rule changes, semantics - re-statement, lemma migration, preservation reproof). -. **L2 — Modality parameter** - - Promote `is_linear_ty` from a per-type predicate to a judgment - parameter `ℓ ∈ {Linear, Affine}`. - - Implement `weaken_modality : Linear → Affine` as a single - induction. - - Restate the existing dual-grammar story (in `ephapax-linear/`) - in terms of the modality. -. **L3 — Echo residue** - - Add `formal/Echo.v` mirroring `echo-types/proofs/agda/Echo.agda`. - - Introduce `TEcho ⟨op⟩` and the `T_Observe` rule. - - Modify `S_Region_Exit` and `S_Drop` to produce residue values. -. **L4 — Mode declaration** - - Project-level metadata (`Cargo.toml` or `ephapax.toml`). - - Type checker reads the declaration and selects the L2 modality. - - No proof obligations. - -The previous "Preservation closure plan" section is **archived** — -see `formal/PRESERVATION-HANDOFF.md` for the historical record. -``` - -### 12.6 spec/SPEC.md and spec/ephapax-spec.md — the canonical spec - -The spec is the binding contract for implementers. It must: - -- Define `R_in / R_out` threading on the typing judgment. -- State the L1 sibling-safety invariant as a theorem. -- Mark L3 (Echo) as "future extension; not normative". -- Reference `EPHAPAX-VISION.adoc` for the dyad framing. - -Add an introduction paragraph: - -> Ephapax's type system has four orthogonal disciplines: **L1** -> region capabilities, **L2** structural modality (linear/affine), -> **L3** irreversibility residue (Echo Types — planned), and **L4** -> dyadic interaction mode (project-level declaration). This -> specification is normative for L1 and L2 and forward-looking for -> L3 and L4. - -### 12.7 ephapax-linear/README.md — sublanguage docs - -`ephapax-linear/` hosts both the linear and affine checkers (the -crate name predates the dyadic naming). Its README currently compares -the two grammars. Add a banner at the top: - -```markdown -> **Naming note.** This crate is called `ephapax-linear` for -> historical reasons; it implements **both** L2 modalities -> (Linear and Affine). The two are not different languages — they -> are two admissible-derivation regimes over the same syntax and -> semantics. See `docs/vision/EPHAPAX-VISION.adoc` for the dyad, -> and `formal/PRESERVATION-DESIGN.md §5` for the L2 layer. -``` - -Update the comparison table title from "Dual Substructural Grammars" -to "Two L2 Modalities". - -### 12.8 CLAUDE.md — agent guidance - -CLAUDE.md already has the AffineScript disambiguation (correct, keep). -Add a section after the disambiguation block: - -```markdown -## The four orthogonal layers - -When working in this repo, the typing system has four layers. Knowing -which layer a question touches is the first step in answering it: - -| Layer | One-line question to ask | -|---|---| -| L1 — Region capabilities | "Does this involve `R_in`, `R_out`, or `In r R`?" | -| L2 — Structural modality | "Is this about consumption, weakening, or Linear vs Affine?" | -| L3 — Echo residue | "Is this about *what was lost* when an irreversible step fired?" | -| L4 — Dyadic mode | "Is this a project-level mode declaration question?" | - -Most questions touch exactly one layer. The design rationale is in -`formal/PRESERVATION-DESIGN.md`. The verified counterexample that -forced the redesign is in `formal/Counterexample.v`. - -Standing rule: if a proposed change appears to require a side -condition on a compound typing rule (e.g. "the sibling doesn't -reference this region"), pause — the four-layer threading should make -that side condition *derivable*, not stated. -``` - -### 12.9 CHANGELOG.md — the next entry - -```markdown -## [Unreleased] - -### Architecture - -- **Four-layer typing redesign** (`formal/PRESERVATION-DESIGN.md`). - The original "linear+affine + regions" framing admitted a verified - counterexample to preservation (`formal/Counterexample.v`). The - redesign separates four orthogonal disciplines: region capabilities - (L1), structural modality (L2), irreversibility residue (L3, - planned), and dyadic interaction mode (L4). Each is a thin-poset - decoration, composing without coherence obligations. - -### Proof / theory - -- Verified counterexample to preservation in the current rules - (`formal/Counterexample.v` — three lemmas `Qed`). The - counterexample is the canonical regression test for the L1 fix. - -### Docs - -- New design document: `formal/PRESERVATION-DESIGN.md`. -- README, EXPLAINME, EPHAPAX-VISION, ROADMAP, CLAUDE updated to - reflect the four-layer story. -- Repo description and tagline updated. -``` - -### 12.10 site/index.md and the GH Pages homepage - -Wherever the homepage exists (`site/index.md` is the current -candidate; the repo also has `homepage = https://hyperpolymath.github.io/ephapax/`), -update the hero block: - -```markdown -# Ephapax - -> Four-layer dyadic type system for WebAssembly memory safety. - -Four orthogonal disciplines compose to guarantee compile-time -memory safety without a garbage collector: - -- **Regions** (L1) — capabilities threaded through every expression. -- **Linear ↔ Affine** (L2) — the dyad, mother and child. -- **Echo** (L3, planned) — irreversibility leaves typed residue. -- **Dyadic mode** (L4) — declare which side you speak from. - -Mechanically formalised in Coq and Idris2. See -[design](formal/PRESERVATION-DESIGN.md) · -[vision](docs/vision/EPHAPAX-VISION.adoc) · -[spec](spec/SPEC.md) · -[roadmap](ROADMAP.adoc). -``` - -### 12.11 TOPOLOGY.md — the map - -TOPOLOGY.md describes the repo's directory layout. Add a column -mapping each top-level dir to which layer(s) it implements: - -```markdown -| Path | Purpose | Layer(s) | -|---|---|---| -| `src/ephapax-typing/` | type checker | L1, L2 | -| `src/ephapax-syntax/`, `src/ephapax-surface/` | AST | L1, L2 (same syntax) | -| `ephapax-linear/` | dual grammars + checkers | L2 | -| `formal/` | Coq mechanisation | L1 (L2 + L3 planned) | -| `idris2/`, `src/abi/Ephapax/` | Idris2 mechanisation | L1 | -| `docs/vision/`, `EPHAPAX-VISION.adoc` | dyad framing | L4 | -| `spec/` | normative spec | L1, L2 | -| (future) `formal/Echo.v` | residue mechanisation | L3 | -``` - -### 12.12 .machine_readable/6a2/*.a2ml — the structured metadata - -These files (`STATE.a2ml`, `META.a2ml`, `ECOSYSTEM.a2ml`, -`AGENTIC.a2ml`, `NEUROSYM.a2ml`, `PLAYBOOK.a2ml`) feed downstream -tooling (hypatia, k9, the cartridge story). Each needs a structural -update: - -| File | Update | -|---|---| -| `STATE.a2ml` | Add `@architecture: four-layer-typing-redesign` block; mark L1 in-flight, L2 partial-existing, L3 planned, L4 planned. | -| `META.a2ml` | Add `@layers:` array listing L1–L4 with one-line descriptions. Cross-reference echo-types as upstream theory dependency. | -| `ECOSYSTEM.a2ml` | Add `@relates_to: echo-types` (theory) and update `@guarantees:` to name L1 + L2 invariants. | -| `AGENTIC.a2ml` | Add a disambiguation block: "L1/L2/L3/L4 are layer names; ephapax-linear/ephapax-affine are L2 modes; Linear/Affine in echo-types are L3 modes — names overlap deliberately because the underlying poset is the same; agents must disambiguate by context." | -| `NEUROSYM.a2ml` | If it carries proof-state vectors, update preservation status: `closed-with-redesign-planned` (was `open-12-admits`). | -| `PLAYBOOK.a2ml` | Add an entry: "when a proof attempt requires sibling region disjointness as a side condition, escalate to L1 redesign rather than patching." | - -### 12.13 GitHub wiki - -`gh repo view` confirms wiki is enabled (`hasWikiEnabled: true`) but -the wiki has no content surveyed in this session. Recommended page -structure (clone `git clone git@github.com:hyperpolymath/ephapax.wiki.git` -locally, or create via the wiki UI): - -| Page | Content | -|---|---| -| `Home` | Hero + four-layer summary + links into the repo (vision, design, spec, roadmap). | -| `The-Four-Layers` | Long-form explainer of L1–L4, with diagrams. Same content as the new README §"The four layers" but expanded with motivating examples. | -| `The-Dyad` | Excerpt + adapt from `EPHAPAX-VISION.adoc`'s "The Dyad" and "One Language, One Feel" sections. Public-facing. | -| `Region-Capabilities` | Long-form L1 explainer with the counterexample walked through (this is the **selling point** for the redesign — make it visceral). | -| `Echo-Types-and-Residue` | L3 explainer, citing echo-types as upstream. Mark as "planned" prominently. | -| `Dyadic-Mode-Declaration` | L4 page; mode declarations, when each fits. | -| `Soundness` | Index of what is proved, where, and at what status. Cross-references `formal/PRESERVATION-DESIGN.md` and `Counterexample.v`. | -| `FAQ` | Includes the disambiguation against AffineScript, the relationship to echo-types, why four layers (not three or five), and why the redesign was forced rather than chosen. | -| `Glossary` | Terms: region capability, modality, echo, residue, dyadic mode, thin-poset decoration. | - -Wiki updates should happen **after** the repo-side docs land, so the -wiki can link to merged sources rather than to in-flight files. - -### 12.14 Cross-cutting: language for new framing (drop-ins) - -These passages are written once and dropped wherever needed: - -**One-sentence what-this-is**: -> Ephapax is a dyadic programming language whose type system composes -> four orthogonal disciplines — region capabilities, structural -> modality, irreversibility residue, and dyadic mode — to guarantee -> compile-time WebAssembly memory safety without a garbage collector. - -**One-paragraph what-this-is**: -> Ephapax is a programming language for WebAssembly with a -> mechanically verified type system. Four orthogonal disciplines -> compose to give the safety guarantees: **region capabilities** (L1) -> thread an explicit live-region set through every expression so a -> sibling cannot reference a region another sibling has exited; -> **structural modality** (L2) chooses between Linear (every linear -> binding must be consumed) and Affine (linear bindings may be -> dropped); **irreversibility residue** (L3, planned) makes the -> information lost by region exit and drop into a first-class, -> proof-relevant value; and **dyadic mode** (L4) declares, at the -> project level, which side of the linear/affine dyad the program -> speaks from. The four layers compose without coherence obligations -> because each is a thin-poset decoration, a construction borrowed -> verbatim from the `echo-types` mechanisation. - -**Why four layers (FAQ-shaped)**: -> Q: Why four layers, not three or five? -> -> A: The four are what the soundness story demands. L1 is forced by -> a verified counterexample to preservation: sibling-safe region -> exit cannot be enforced without explicit capability threading. L2 -> already exists in the implementation as the linear/affine split; -> the redesign promotes it from a per-type to a per-judgment notion -> and proves it Linear ⊆ Affine. L3 is required because Linear mode -> demands that irreversibility be *witnessed*, not silently -> performed; echo-types supplies the formal machinery. L4 is the -> project-level dial that selects which way L2 and L3 default. Drop -> any one and the architecture has a hole; add a fifth and you -> duplicate an existing layer. - -**Why not just patch preservation (FAQ)**: -> Q: Why redesign the type system rather than patch the proof? -> -> A: `formal/Counterexample.v` is `Qed`: it exhibits a well-typed -> input that single-steps to an untypable output, at the same outer -> type. That is preservation *failing*, not preservation -> *unproven*. The fix isn't a tactic; it's a missing invariant. We -> add the invariant explicitly (L1 threading) rather than via ad-hoc -> side conditions on every compound rule, because sibling-region- -> disjointness should be a *theorem* about the new threading, not a -> premise on every rule. - -### 12.15 Proof debt: what is proved today, what is **not**, and what this redesign will / will not change - -This subsection is required reading before any future claim about -"ephapax preservation" or "ephapax soundness" is made — by a human -or by an agent. The architecture above describes the **target**; -this subsection describes the **current state**, which is much -narrower than the target. - -#### 12.15.1 What is mechanised today - -| Mechanisation | Scope | Status | -|---|---|---| -| `formal/Semantics.v`, `formal/Typing.v`, `formal/Syntax.v` (Coq) | A **single** typing judgment `R; G ⊢ e : T -| G'` and its small-step operational semantics | Builds with `coqc 8.18.0` | -| `formal/Counterexample.v` | A verified counterexample to preservation **as currently stated** | All three lemmas `Qed` | -| `preservation` theorem in `Semantics.v` | Soundness under the current single judgment | **`Admitted.`** — 11 cascading goals open (see `PRESERVATION-HANDOFF.md`) | -| `src/abi/Ephapax/…/*.idr` (Idris2) | Selected structural-safety claims | Per file; see `idris2 --check` | - -#### 12.15.2 What the single judgment **is not** - -The current `has_type` judgment **does not distinguish** Linear from -Affine ephapax. It is a single typing relation, parameterised over a -linearity context `G` whose entries carry a `(ty, bool)` consumption -flag. `is_linear_ty` returns a per-type tag. There is **no judgment -parameter** for L2 modality; there is **no proof** that Linear -derivations form a subset of Affine derivations; there is **no -mechanised ephapax-affine type system** at all. - -Concretely: the Rust crate `ephapax-linear/` contains a -`LinearChecker` and an `AffineChecker`, with EBNF grammars and -behavioural tests proving they diverge on identical programs. That is -the implementation side of the dyad. The **proofs side** has not -caught up: the Coq + Idris2 formalisation models *one* judgment, -which behaviourally resembles the linear discipline (it requires -linear-typed bindings to be consumed) but is not labelled as such -and has no proven dual. - -#### 12.15.3 What this redesign **will** establish - -If executed as designed, the redesign closes: - -- **L1 — region capabilities**: a new threading discipline + a - reproof of preservation under the new rules. This is the central - deliverable. The verified counterexample becomes a passing - regression. -- **L2 promotion**: the judgment gains an `ℓ ∈ {Linear, Affine}` - parameter, and a `weaken_modality : Linear ⇒ Affine` lemma. This - is what *introduces* a mechanised ephapax-affine into the formal - development for the first time. - -#### 12.15.4 What this redesign **will not** establish - -Equally explicitly — so future sessions, agents, and PR reviewers do -not assume otherwise: - -- **The ephapax-affine type proofs are not done today, and the L1 - redesign on its own does not finish them.** L1 closes preservation - under the *single* judgment that already exists. L2's - modality-weakening lemma, once proved, will lift L1's preservation - to the affine side — but that lift is a *separate* lemma that has - not been written and is not part of the L1 patch. Until L2 lands - *and* the weakening lemma is `Qed`, **no ephapax-affine soundness - property is mechanised**. -- **No L3 (Echo / residue) proof obligation is closed.** L3 is - forward-looking. Until `formal/Echo.v` exists and references it - from the operational semantics, the Linear-echo "mandatory - observation" property and the Affine-echo "non-duplicable - residue" property are *aspirations*, not theorems. -- **No proof that the Rust `AffineChecker` agrees with any - mechanised affine judgment.** The implementation-side dual exists; - the mechanisation-side dual does not. Cross-checking the two is a - third, independent body of work. -- **No proof of the L4 mode declaration's consistency** (e.g., that - a program declared Linear cannot accidentally invoke an Affine- - typed library through a hole in mode-elaboration). L4 is a UX - layer in this design; making it sound requires further work that - is not scoped here. - -#### 12.15.5 The honest one-line summary - -> **Ephapax's mechanised soundness story today is: one judgment, -> resembling linear discipline, with preservation `Admitted`. The -> verified counterexample shows preservation false as stated. The L1 -> redesign in this document closes that counterexample. Ephapax- -> affine proofs as a distinct mechanised body of theorems are not -> done, will not be done by L1 alone, and will require L2 plus a -> follow-up weakening lemma before any "ephapax-affine is sound" -> claim is honest.** - -Cite this subsection by section number (`§12.15`) whenever the -question of "what is proved" is asked. Do not paraphrase the bounds -upward. - -### 12.16 Future research track — "Echo as foundation" (v2) - -The four-layer design in this document treats **Echo as L3**: a residue -layer over the existing operational primitives (region exit, drop). -This is interpretation **B** of the question "can Echo replace the -reclamation model in ephapax?" — Echo *augments* the existing region -model; it does not replace it. - -Interpretation **A** is a coherent research direction, but a much -larger one: making Echo the **operational foundation** itself. In an -A-shaped calculus: - -- `ERelease v` becomes a primitive operation: yields a residue value - of type `TEcho ⟨op⟩` *and* releases the underlying memory. -- `region r { … }` becomes **syntactic sugar** for a sequence of - releases at scope exit, in reverse-allocation order. -- The operational semantics' primitive step is the release event, - not the region exit. -- Linear-mode discharge becomes a **termination invariant**: a - closed Linear program with no outstanding echo obligations is - provably leak-free. -- Surface-language UX research is required: implicit-at-scope-exit - vs explicit `release x` vs both. Borrow semantics under deferred - release is a real theory question, not a notation question. - -**This is a sibling v2 calculus, not a patch.** It will require its -own Coq development (`formal/Semantics_v2.v` or a separate -`ephapax-echo-foundation/` crate), its own design doc -(`formal/ECHO-AS-FOUNDATION.md`, currently not written), and its -own roadmap. The migration story (current ephapax programs ↦ v2 -calculus) is itself a research question. - -**Why this is staged separately from B:** - -1. **B closes preservation in weeks; A is months.** Preservation is - currently `Admitted` with a verified counterexample. Closing it - is more urgent than v2 architectural exploration. -2. **B and A are not mutually exclusive.** Once B lands, A can be - developed as a parallel branch using B's primitives. The L3 echo - layer in B is a *prerequisite* for A's foundation rewrite — - nothing wasted. -3. **Echo-types itself follows this restraint.** Its formal - contribution is editorial reframing of homotopy fibers as - irreversibility residues, not a new operational foundation. The - same restraint applies here: Echo-as-L3 is the addition; Echo-as- - foundation is a v2 design that earns its keep through - experimentation, not by decree. - -**Concrete next steps for A** (when undertaken; not now): - -| Step | Output | -|---|---| -| 1. Design doc | `formal/ECHO-AS-FOUNDATION.md` — primitives, semantics, type rules | -| 2. Borrow-under-release theory | Resolve how borrows extend across release points; mechanise | -| 3. Coq prototype | `formal/Semantics_v2.v` with `ERelease` / `EObserve` primitives | -| 4. Linear discharge as termination invariant | Stronger than B's Linear-discharge theorem | -| 5. Region desugaring equivalence | Prove `region r { e }` ≡ sequenced releases of e's allocations | -| 6. Surface-syntax UX research | Implicit-vs-explicit release; default-scope semantics; ergonomic patterns | -| 7. WasmGC interop boundary | Explicit boundary types between echo-tracked and GC-tracked values | -| 8. Migration plan | Map v1 ephapax programs ↦ v2; most code should be syntactic-sugar-preserved | - -**Tracking**: when the time comes, file as an issue -`ephapax: research — Echo as operational foundation (v2 calculus)` -with this section as the seed. Roadmap entry below. - -Add to `ROADMAP.adoc` under "Future research tracks": - -```adoc -=== Echo as operational foundation (v2) - -The four-layer redesign (`formal/PRESERVATION-DESIGN.md`) treats Echo -as a residue layer (L3) over regions. A coherent v2 research direction -makes Echo the *foundation* itself: `ERelease` as a primitive -operation, regions desugared to sequenced releases, Linear-mode -discharge as a termination invariant. - -**Not scheduled.** This is a months-long research effort, staged -after the L1+L2+L3 layer-redesign lands. See -`formal/PRESERVATION-DESIGN.md §12.16` for the seed; an -`ECHO-AS-FOUNDATION.md` design doc will be opened when the work -begins. -``` - -### 12.17 Execution order - -The user has not asked for these edits to be made yet. Execution -order, when they are: - -1. Land `formal/PRESERVATION-DESIGN.md` (this document). -2. Update README + EXPLAINME + EPHAPAX-VISION in one PR - ("docs: introduce four-layer framing"). -3. Update ROADMAP + CHANGELOG in one PR ("docs: roadmap reflects - four-layer redesign; counterexample as regression"). -4. Update CLAUDE + .machine_readable a2ml in one PR ("docs(agent): - layer-aware guidance"). -5. Update spec/ in one PR ("spec: introduce L1 R-threading; L3 - forward-looking"). -6. Update site/index.md + repo description + tagline in one PR ("site: - four-layer hero"). -7. Wiki updates last, out-of-band (wiki commits don't go through PR - review). - -All in-repo PRs follow standing policy: GPG-signed, auto-merge ON, -GH conventions matched. - diff --git a/formal/PRESERVATION-HANDOFF.adoc b/formal/PRESERVATION-HANDOFF.adoc new file mode 100644 index 00000000..e3908eb0 --- /dev/null +++ b/formal/PRESERVATION-HANDOFF.adoc @@ -0,0 +1,818 @@ +Copyright (c) Jonathan D.A. Jewell j.d.a.jewell@open.ac.uk –> + +== Hand-off: closing `+preservation+` in `+formal/Semantics.v+` + +____ +== 🛑 ARCHAEOLOGY ONLY — do NOT follow the closure plans in this file + +The verified counterexample at `+formal/Counterexample.v+` (five lemmas +`+Qed.+`) shows `+Theorem preservation+` in `+formal/Semantics.v+` is +*provably false*. The reduction-from-910-goals story below is the +*historical record* of attempts that, at the time, looked like they +would close it. None of them work — preservation cannot be closed +because no proof exists. + +*DO NOT* spend session time on: - The 5-phase "`Preservation closure +plan`" (Lemma B / region-weakening / Phase 5 Qed flip) — that section +has been removed from `+ROADMAP.adoc+` as of 2026-05-27. - Continuing +the 12 → 0 reduction story. - Extending the legacy `+Semantics.v+` with +new lemmas to support any of the above (`+step_preserves_type_at_pre+`, +`+step_output_context_eq_at_pre+`, etc. are all in this class). + +The canonical design for the fix is the *four-layer redesign* in +`+formal/PRESERVATION-DESIGN.md+`. Preservation is re-derived per-layer +(`+preservation_l1+` in `+Semantics_L1.v+`, etc.), not closed against +the legacy judgment. The owner directive (2026-05-27) is in +`+CLAUDE.md+` at the repo root. + +This file is retained so the per-case diagnostic data (which congruence +patterns hit which obstacle, etc.) remains available to inform the +layered design — but reading it as a closure plan is the trap. +____ + +Diagnostic + remediation log. The proof is still `+Admitted.+`, but as +of *2026-05-21* it’s *down from 910 open goals to 12* via four landed +PRs. This file tells whoever picks it up next exactly what’s open and +what the canonical closure path is. + +____ +*The canonical closure plan is now in `+formal/PRESERVATION-DESIGN.md+`* +(four-layer redesign; L1 region capability threading). The prior +"`Preservation closure plan`" in `+ROADMAP.adoc+` is superseded; this +file remains as the per-case diagnostic record. Read the design doc +first; come back here for case detail and the historical closure-attempt +log. +____ + +=== State at a glance + +[width="100%",cols="24%,>48%,28%",options="header",] +|=== +|Date |Open goals |Notes +|2026-04-27 |"`fully closed`" |In-file comment — but `+coqc+` rejected +the `+Qed.+`. The claim was unsubstantiated; the proof never closed. + +|2026-05-20 (am) |910 |Discovered via `+Show. Show Existentials.+` +before the `+Admitted.+`. Exactly 35 (step rules) × 26 (typing rules). +The existing `+try solve [...]+` chain closes ZERO. + +|2026-05-20 (pm) |29 |After the standard preservation pattern +(`+remember (mu, R, e) as cfg+` + symmetric for cfg’, then +`+inversion Hcfg; subst; inversion Hcfg'; subst;+` inside each case). +97% reduction. PR #102. + +|2026-05-20 (eve) |22 |After `+revert mu R e mu' R' e' Hcfg Hcfg'+` +before `+induction Hstep+` so each case’s IH carries universal +quantification over the inner step’s config. PR #106. + +|2026-05-20 (eve) |22 |Region-invariance lemma +`+step_R_eq_or_touches_region+` landed as infrastructure (no goal +closures). PR #114. + +|2026-05-20 (night) |*12* |10 β-reduction / value-step cases discharged +via per-case manual proofs using the lemma. PR #116. *98.7% reduction +across one day.* + +|2026-05-24 |Lemma B 4/35 closed |`+step_output_context_eq+` scaffolded +with `+cfg+`-remember pattern. Atomic-axiom tactic closes 4 step rules. +PRs #121/#124/#126. + +|2026-05-24 (late) |Lemma B 31/35 closed |Cluster A (β-reduction, 7) +*FULLY CLOSED* via `+subst_preserves_typing_strong+` + +`+output_ctx_det+`. Cluster C (region/compound-value, 6) *FULLY CLOSED* +via inversion + `+value_context_unchanged+`. Cluster B (congruence) 9 of +18 closed via R-shape dispatch. + +|2026-05-26 |*1 + 1 + 12* |Empirical `+coqc 8.18.0+` re-verification: 1 +admit in `+step_preserves_type+` (Semantics.v:4885), 1 admit in +`+step_output_context_eq+` (Semantics.v:5963), 12 cascading goals in +`+preservation+`. The two upstream admits are the SAME structural +sub-case — S_Region_Step’s `+r = r1+` "`exited from inside`" — mirrored +across both lemmas. + +|2026-05-26 (eve) |*0 + 0 + 12* (Qed × 2) |*Path 3 (at-pre helper) +lands.* Both upstream lemmas are now `+Qed+`. Introduced two NEW helper +lemmas (`+step_preserves_type_at_pre+` and +`+step_output_context_eq_at_pre+`) whose typings are at the SHARED +pre-step env R. The S_Region_Step cross-case +`+T_Region_Active × T_Region+` collapses to a contradiction (In r R vs +~In r R at same env) — sidestepping the original obstacle. Plug-in via +`+region_env_perm_typing+` + `+remove_first_then_cons_membership_eq+` +(existing, Qed). The 12 cascading goals in `+preservation+` remain — +they’re a SEPARATE structural problem, not the shared admit. + +|2026-05-26 (late eve) |*Qed × 4 + 11* |*Swarm A + B + C complete.* +`+step_preserves_type_at_pre+` and `+step_output_context_eq_at_pre+` are +now Qed (Swarm A + B per-goal closures landed). Swarm C oracle splice +closes all 10 congruence LEFT branches in `+preservation+` via +`+step_output_context_eq+` (Qed) — +`+assert Gmid = Gout by (eapply step_output_context_eq; …); subst+` +unifies the IH’s existential output context with the sibling’s Gmid. +`+S_StringLen_Step+` closes ENTIRELY as vacuous (`+EBorrow+`’s inner is +`+EVar+` or value, neither steps — pattern lifted from `+S_Borrow_Step+` +elsewhere in this proof). 11 admits remain in `+preservation+`: 10 +RIGHT-only sub-cases (`+HTR : touches_region+` in scope) + 1 +`+S_Region_Step+` (special). All 10 RIGHT cases share the same shape and +block on the same region-env weakening lemma for non-values (= Brief C, +structural follow-up). + +|2026-05-26 (night) |*Qed × 4 + 11* (narrowed) |*MIDDLE narrowing +lands.* Converted all 10 congruence `+S_*_Step+` blocks from 2-way +(`+step_R_eq_or_touches_region+`) to 3-way (`+step_R_change_shape+`) +dispatch. LEFT branch unchanged. *MIDDLE (R’ = rw :: R, push)* now +closes via the same `+step_output_context_eq+` oracle on the stepped +child + `+region_add_typing+` to lift the sibling typing from `+R+` to +`+rw :: R+`. RIGHT (`+R' = remove_first r R+`, pop) remains `+idtac+`. +Result: each of the 10 admits is now narrower — +`+Hrem : R0' = remove_first r R0+` and `+HinR : In r R0+` are in scope +(vs the broader `+HTR : touches_region+` before). Brief C’s required +lemma reduces from "`region-env weakening across any touches_region +step`" to "`pop-only weakening`" (the surviving sibling must be +transportable from `+R+` to `+remove_first r R+` when `+In r R+`). Coq +subtlety: the 2 S_StringConcat blocks must use `+[rw …]+` instead of +`+[r …]+` in the destruct pattern because the outer T_StringConcat +inversion binds an `+r+` (the TString region) in scope already. +|=== + +____ +*Path 3 (landed 2026-05-26 eve):* The Option 2 plan (structural +recursion deriving `+expr_free_of_region+`) was *blocked* by the +`+ELet (ERegion r v_inner) (ELoc l r)+` counterexample (sibling +references to r survive the exit, so `+expr_free_of_region r e'+` is +false in general). Path 1 (mutual induction) would have been a heavy +refactor (~8-12h). + +Path 3 introduces TWO helper lemmas whose signatures match the upstream +pair except both typings are at the SHARED pre-step env R (not R/R’). In +this framing, the S_Region_Step cross-case +`+T_Region_Active × T_Region+` collapses to a vacuous contradiction +(`+In r R+` vs `+~In r R+` at the same env), sidestepping the obstacle. +The plug-in then uses the existing `+region_env_perm_typing+` to +transport the body’s typing from `+r :: remove_first r R0+` to `+R0+` +(membership-equivalent when `+In r R0+`). Net code change: ~100 lines + +2 helper bodies (~1600 lines copy-paste from upstream). Wall-clock: +*~3h* to land both Qed flips. + +*Status of the helpers themselves (2026-05-26 late eve, commit +`+d6ebf68+`):* both are now `+Qed+`. The mass-copied Cluster A/B/C +tactic blocks (verbatim from the upstream lemmas, patterns use +`+?R+`/`+?R'+` polymorphically) close the bulk; the residual cases that +didn’t fit those patterns are closed by explicit `+1: {...}+` per- goal +blocks at the tail of each helper — 5 in `+step_preserves_type_at_pre+` +(S_StringConcat_Step2, S_App_Step2, S_Snd atomic, S_Case_Step, S_Copy +atomic) and 11 in `+step_output_context_eq_at_pre+` (the listed S_*_Step +congruence cases). The upstream `+step_preserves_type+` and +`+step_output_context_eq+` `+Qed+` chain is therefore non-axiomatic. + +*Status of preservation:* 12 cascading goals remain, expected to still +need a region-env weakening for non-values (per the original analysis +below). The at-pre helpers DON’T directly help with these — they help +with the UPSTREAM lemma obstacle, not preservation’s own touches_region +RIGHT branch. Re-diagnosis is in progress now that step_preserves_type + +step_output_context_eq are available as oracles. +____ + +=== What the 910 → 29 fix did + +The prior `+induction Hstep+` did not substitute the outer expression +slot `+e+` to the constructor’s form — so `+inversion Htype+` produced +all 26 typing arms per step rule instead of just the diagonal. Cross- +cases (e.g. `+S_StringNew+` step + `+T_Unit+` typing) had no +discriminating equation in scope, so +`+try solve [exfalso; discriminate | exfalso; congruence]+` couldn’t +fire. + +The fix: + +[source,coq] +---- +intros mu R e mu' R' e' Hstep. +remember (mu, R, e) as cfg eqn:Hcfg. +remember (mu', R', e') as cfg' eqn:Hcfg'. +induction Hstep; intros G0 T0 G0' Htype; + inversion Hcfg; subst; + inversion Hcfg'; subst; + inversion Htype; subst; + (* … existing try-solve chain … *) +---- + +`+remember+` turns `+Hstep : step (mu, R, e) (mu', R', e')+` into +`+Hstep : step cfg cfg'+` with two side equations +`+Hcfg : ... = (mu, R, e)+`, `+Hcfg' : ... = (mu', R', e')+`. Now +`+induction Hstep+` substitutes the constructor’s "`from`" config into +`+cfg+` and the equation `+Hcfg+` becomes (for `+S_StringNew+`) +`+(mu0, R0, EStringNew r s) = (mu, R, e)+`. `+inversion Hcfg; subst+` +decomposes this and substitutes `+e := EStringNew r s+` everywhere — +including in `+Htype+`. Then `+inversion Htype; subst+` only generates +the diagonal `+T_StringNew+` arm; the 25 cross-arms are eliminated by +inversion’s constructor- mismatch check. + +The earlier `+remember e_typed as e_orig eqn:He_orig+` (the original +"`preserve discriminating equation`" attempt) was a misdiagnosis — it +remembered the _typing’s_ `+e+`, which was already abstract; the problem +was that the _config’s_ expression slot was abstract. + +=== How to reproduce the diagnostic + +[source,coq] +---- + end). +Show. (* prints current goal *) +Show Existentials. (* prints all 29 unresolved metavariables *) +Admitted. +---- + +then: + +[source,sh] +---- +cd formal +coq_makefile -f _CoqProject -o Makefile.coq && make -f Makefile.coq +---- + +`+coqc+` prints goal count + every open existential. Restore the +`+Admitted.+` afterwards. Same recipe (now yielding 29 instead of 910) +is the per-case work list. + +=== The 29 remaining goals (per-case checklist) + +==== Axiom cases needing explicit reconstruction (~3 goals) + +[width="100%",cols="31%,29%,40%",options="header",] +|=== +|Step rule |Goal needs |Tactic sketch +|`+S_StringNew+` |type `+ELoc l r+` at `+TString r+` +|`+eexists; eapply T_Loc; eauto using mem_alloc_lookup+` + +|`+S_StringConcat+` |type `+ELoc l' r+` at `+TString r0+` |same as +above, with the new alloc + +|`+S_StringLen+` |type `+EI32 (String.length s)+` at `+TBase TI32+` +|`+eexists; constructor+` +|=== + +==== β-reduction cases needing `+subst_preserves_typing+` (~6 goals) + +[width="100%",cols="58%,42%",options="header",] +|=== +|Step rule |Tactic +|`+S_Let_Val+`, `+S_LetLin_Val+`, `+S_App_Fun+`, `+S_Fst+`, `+S_Snd+`, +`+S_Case_Inl+`, `+S_Case_Inr+` +|`+eexists; eapply subst_preserves_typing; eauto+` (most should fall +through the existing chain once the expression slots are concrete) + +|`+S_If_True+`, `+S_If_False+` |`+eexists; eassumption+` (the branch +typing is already in context) +|=== + +==== Congruence cases needing IH + reconstruction (~15 goals) + +For each `+S_*_Step+`: +`+destruct (IHHstep ...) as [G_out Hout]; eexists; econstructor; eauto+`. +The IH’s form is awkward after `+remember+` — contains the +`+Hcfg+`/`+Hcfg'+` equations as extra premises. May need +`+specialize IHHstep with (1 := Heqcfg_inner) (2 := Heqcfg'_inner)+` +first, or restructure to revert + induct on typing instead of step. + +==== Region cases (~3 goals) + +[width="100%",cols="58%,42%",options="header",] +|=== +|Step rule |Status +|`+S_Region_Enter+` |typing reconstruction (`+T_Region_Active+` + +`+In r (r::R)+`) — the existing +`+try solve [eexists; eapply T_Region_Active; ...]+` should fire + +|`+S_Region_Exit+` |needs `+region_shrink_preserves_typing+` (existing — +Qed) + the `+expr_free_of_region+` step premise + +|`+S_Region_Step+` + `+T_Region_Active+` |*the bottleneck* — needs a +*region-env _weakening_ lemma for non-values* that does not yet exist. +Roughly: +`+forall R e G G' T r, R; G \|- e : T -\| G' -> ~ In r R -> e is not a value -> (r :: R); G \|- e : T -\| G'+`. +The "`not a value`" precondition is delicate — naive weakening is +unsound for `+EBorrow+` and friends. Probably 50–150 LOC by itself. +|=== + +==== Linear cases (~2 goals) + +[width="100%",cols="58%,42%",options="header",] +|=== +|Step rule |Tactic +|`+S_Drop+` |`+eexists; constructor+` (drop yields +`+EUnit : TBase TUnit+`) + +|`+S_Copy+` |`+eexists; constructor; assumption+` (copy yields +`+EPair v v : TProd T T+`) +|=== + +=== What it would take to finish + +Realistic estimate: *days, not weeks* — the 29 remaining goals are all +standard preservation-proof glue except for the region-env weakening +lemma. The lemma’s design (which non-value carriers admit weakening, +which don’t) is the only genuine theory question. The mechanical 28 are +1–2 days of tactic work. + +=== What is NOT a fix + +* Adding more `+try solve [...]+` lines to the existing chain at random. +Run `+Show. Show Existentials.+` first to see what’s actually open. +* Replacing `+induction Hstep+` with `+inversion Hstep+` — that loses +the IHs needed for congruence cases. +* Mass-`+Admitted.+` per case — defeats the point and conflicts with +estate’s "`build is the only oracle`" policy. The honest mark is one +`+Admitted.+` on `+preservation+`, not 29. + +=== `+step_preserves_type+` per-case status (2026-05-24, late) + +Empirically verified against `+coqc 8.18.0+`. The 12 cases identified as +"`remaining open`" after the f499c82 clone-out have now each been given +an explicit per-case proof skeleton. *Three iterations*: + +[arabic] +. *First pass*: dispatched on `+step_R_eq_or_touches_region+`, closed +the LEFT (R = R’) branches, admitted RIGHT. 8 admits remained. +. *Second pass*: introduced finer `+step_R_change_shape+` (3-way +disjunction: equal / prepend r / remove_first r), closed the MIDDLE +(prepend) branch for all 7 congruence cases via `+region_add_typing+`. +Still 8 admits, but each tied to the strictly-narrower "`remove_first r +R`" sub-case rather than the broader "`touches_region`". +. *Third pass*: added the `+remove_first_then_cons_membership_eq+` +helper (proving `+r :: remove_first r R+` and `+R+` have the same +membership whenever `+In r R+`, no `+NoDup R+` required) and used it +with `+region_env_perm_typing+` to close the RIGHT (`+remove_first+`) +sub-case in all 7 congruence proofs. *Now 1 admit remains*, tied to +S_Region_Step’s exfalso when the inner step exits the outer region from +inside (r = r1 sub-case). + +*Moved* `+touches_region+` + `+step_R_eq_or_touches_region+` to before +`+step_preserves_type+` so the latter can dispatch on the LEFT (R = R’) +branch. + +==== Fully closed (4 of 12) + +[width="100%",cols="24%,42%,34%",options="header",] +|=== +|Goal |Step rule |Closure +|1 |`+S_StringConcat_Step2+` |outer T = TString r is structurally +constrained; IH on (e2, e2’) gives r = r’ + +|8 |`+S_Snd+` atomic (ESnd (EPair v1 v2) → v2) +|`+value_context_unchanged+` on v1 + `+type_determinacy+` on v2 + +|10 |`+S_Region_Exit+` (ERegion r v → v) +|`+region_shrink_preserves_typing+` bridges R0 and remove_first r R0, +then `+type_determinacy+` + +|12 |`+S_Copy+` atomic (ECopy v → EPair v v) +|`+value_context_unchanged+` on first projection + two +`+type_determinacy+` calls on v +|=== + +==== Fully closed via R-shape dispatch (7 of 12 congruence cases) + +After the third pass, every congruence case closes via the 3-way +dispatch on `+step_R_change_shape+`: + +[width="100%",cols="9%,12%,18%,25%,36%",options="header",] +|=== +|Goal |Step rule |LEFT (R = R’) |MIDDLE (R’ = r :: R) |RIGHT (R’ = +remove_first r R) +|2 |`+S_Let_Step+` |✅ |✅ via `+region_add_typing+` |✅ via lift+perm + +|3 |`+S_LetLin_Step+` |✅ |✅ |✅ + +|4 |`+S_App_Step2+` |✅ |✅ |✅ + +|5 |`+S_If_Step+` |✅ |✅ |✅ + +|6 |`+S_Pair_Step1+` |✅ |✅ |✅ + +|7 |`+S_Pair_Step2+` |✅ |✅ |✅ + +|9 |`+S_Case_Step+` |✅ |✅ |✅ +|=== + +The RIGHT sub-case (when the inner step exits a region) is closed by: 1. +`+region_add_typing+` lifts the post-step sibling typing from +`+remove_first r R0+` to `+r :: remove_first r R0+`. 2. +`+region_env_perm_typing+` converts to `+R0+` via +`+remove_first_then_cons_membership_eq+` — the new helper that proves +`+r :: remove_first r R0+` and `+R0+` have the same membership when +`+In r R0+` (NO `+NoDup R+` invariant needed; works even for +duplicates). 3. `+type_determinacy+` aligns the types under the +now-shared `+R0+`. + +==== Partially closed — one sub-case admitted (1 of 12) + +[width="100%",cols="15%,25%,22%,38%",options="header",] +|=== +|Goal |Step rule |Closures |Remaining admit +|11 |`+S_Region_Step+` (ERegion r e → ERegion r e’) |T_Region (Hte) +contradicted by `+In r R0+`; T_Region_Active × T_Region_Active via IH; +T_Region_Active × T_Region 3 of 4 R-shape sub-cases close (R = R’, R’ = +r1::R0, R’ = remove_first r1 R0 with r ≠ r1) — all by contradiction with +`+~In r R0'+` |One sub-case: T_Region_Active × T_Region with +`+R0' = remove_first r R0+` (the outer r is exited from inside). Closing +requires `+expr_free_of_region r e'+` to shrink the post-step inner +typing, derivable but requires further inversion on `+Hstep+` to reach +the underlying `+S_Region_Exit+`’s premise. +|=== + +==== Net effect + +* *Before*: `+step_preserves_type+` was `+all: admit+` with 12 open +goals — every case admitted with no structure. +* *After (pass 1)*: 4 of 12 closed fully, 7 of 12 had LEFT (R = R’) +branch closed, 1 of 12 had main branch closed. 8 admits. +* *After (pass 2)*: each congruence admit narrowed to just the +`+R' = remove_first r R+` sub-case via `+step_R_change_shape+` + +`+region_add_typing+`. Still 8 admits but each strictly narrower. +* *After (pass 3)*: all 7 congruence RIGHT sub-cases closed via the new +`+remove_first_then_cons_membership_eq+` helper + +`+region_env_perm_typing+`. *1 admit remains* — the +`+T_Region_Active × T_Region+` cross-case of `+S_Region_Step+` when the +inner step exits the outer region from inside. + +New supporting lemmas added (all `+Qed.+`): - `+step_R_change_shape+` +(~10 LOC): refines the 2-way disjunction into 3-way, also exposing +`+~In r R+` / `+In r R+` for the prepend / remove cases. - +`+remove_first_then_cons_membership_eq+` (~15 LOC): proves +`+(r :: remove_first r R)+` and `+R+` have the same membership when +`+In r R+`. NO `+NoDup R+` required. + +The single remaining admit is the cross-case where `+Hte+` uses +`+T_Region_Active+` and `+Hte'+` uses `+T_Region+` — meaning the inner +step exits the outer `+ERegion+`’s own region `+r+`, and `+r+` was +unique in `+R0+` (so post-step `+~In r remove_first r R0+`). + +*The genuine obstacle:* `+e'+` may syntactically reference `+r+` even +though `+r+` was unique pre-step. Concrete witness: if +`+e = ELet (ERegion r v_inner) (ELoc l r)+`, then after the inner +`+S_Region_Exit+` we get `+e' = ELet v_inner (ELoc l r)+`. The post-step +sibling `+ELoc l r+` still references `+r+`, so +`+expr_free_of_region r e'+` is false. Yet `+e'+` is well-typed under +`+r :: remove_first r R0+` because `+T_Region+` re-introduces `+r+` at +the head, making `+ELoc l r+` typeable via the freshly-bound `+r+`. This +is the semantic-freshness issue inherent to concrete-name region +encodings: with alpha-renaming the post-step `+r+` would be a distinct +region name; with concrete names the syntactic occurrence persists. + +Consequences for closure paths: - Adding `+NoDup R+` as an invariant +does *not* close it. The `+T_Region+`-vs-`+T_Region_Active+` choice in +`+Hte'+` already encodes uniqueness in scope +(`+H3 : ~In r remove_first r R0+` plus `+H : In r R0+` implies `+r+` +unique). NoDup gives no extra info. - A +`+typing_implies_free_of_absent_region+` lemma doesn’t apply because +`+e'+` is typed at `+r :: remove_first r R0+` where `+r+` IS present (at +the head), so `+~In r R+` doesn’t hold for the typing in scope. - A +`+step_exit_implies_free_of_exited_region+` lemma would be *false* for +congruence cases that preserve siblings — the sibling’s surviving +`+r+`-references break the freedom claim. + +The genuinely-closing options are: 1. *Mutual recursion with +`+preservation+`*: prove `+preservation+` and `+step_preserves_type+` +simultaneously. `+preservation+`’s direct construction of a typing for +`+e'+` at the post-step `+R'+` provides exactly what this admit needs. +Standard textbook approach for region calculi, but a significant +restructuring touching both proofs. 2. *Inversion on `+Hstep+` with +structural recursion*: directly case-split on the step rule path that +produced `+R' = remove_first r R0+`, handling the +`+S_Region_Exit+`-at-top sub-case via region_shrink (works because +`+e' = v+`, free of `+r+` by `+S_Region_Exit+`’s premise), and handling +the congruence-bubbling sub-cases by recursive structural argument on +the wrapping. ~150 LOC, orthogonal to the current case split. + +Both are substantial follow-ups. The current single admit is bounded and +well-documented; closing it should be deferred to whoever takes on (1) +or (2) as a focused effort. + +=== Lemma B per-case status (2026-05-24) + +Empirically verified against `+coqc 8.18.0+`. The Phase 1 scaffold for +`+step_output_context_eq+` now uses the `+cfg+`-remember pattern that +mirrors `+step_R_eq_or_touches_region+` and `+preservation+`, plus an +atomic-axiom closure tactic. *4 of 35 step rules close*; 31 remain. + +==== Closed (4) + +[width="100%",cols="43%,57%",options="header",] +|=== +|Step rule |Why it closes +|`+S_StringNew+` |atomic: `+EStringNew → ELoc+`, both type to +identity-output + +|`+S_StringConcat+` |atomic: `+EStringConcat (ELoc _) (ELoc _) → ELoc+`, +all premises invert to identity-output T_Loc + +|`+S_Drop+` |atomic: `+EDrop (ELoc _) → EUnit+`, both T_Drop and T_Unit +are identity-output + +|`+S_Borrow_Step+` |*accidental congruence closure*: both `+T_Borrow+` +and `+T_Borrow_Val+` output the input context unchanged, so +`+Ga = G = Gb+` regardless of whether the inner step is reachable. +Vacuous-but-closes. +|=== + +==== Open (24, was 31) + +===== Cluster A — β-reduction ✅ FULLY CLOSED (2026-05-24) + +All 7 β-reduction cases closed via `+subst_preserves_typing_strong+` +(PR: this branch) + `+output_ctx_det+` (PR: this branch). Recipe per +case: 1. Invert the outer compound typing (`+T_Let+`, `+T_App+`, +`+T_If+`, `+T_Case+`) to expose body + value premises. 2. For T_App: +also invert `+T_Lam+` on the function value. 3. For T_Case: apply +`+value_context_unchanged+` on the EInl/EInr premise, then invert +`+T_Inl+`/`+T_Inr+`. 4. Apply `+value_context_unchanged+` on the value +premise(s) to align intermediate contexts with the input context. 5. +`+destruct (subst_preserves_typing_strong ...)+` to construct a typing +of the substituted form at the specific output context. 6. +`+eapply output_ctx_det+` against `+Htype_e'+` to conclude `+Ga = Gb+`. + +Closed cases: `+S_Let_Val+`, `+S_LetLin_Val+`, `+S_App_Fun+`, +`+S_If_True+`, `+S_If_False+`, `+S_Case_Inl+`, `+S_Case_Inr+`. + +===== Cluster B — congruence (10 of 18 closed, 8 open) + +*Closed (2026-05-24)*: `+S_StringConcat_Step1+`, +`+S_StringConcat_Step2+`, `+S_Pair_Step1+`, `+S_Pair_Step2+`, +`+S_Inl_Step+`, `+S_Inr_Step+`, `+S_Copy_Step+`, `+S_If_Step+`, +`+S_StringLen_Step+`. (Plus `+S_Borrow_Step+` closed accidentally +earlier.) + +Recipe (canonical two-child congruence, e.g. `+S_StringConcat_Step1+`): +1. Invert both `+Hte+` and `+Hte'+`. 2. +`+pose proof step_R_eq_or_touches_region+` to dispatch `+R = R'+`. 3. +LEFT (R = R’): apply IH on inner step’s typings to get `+Gmid = Gmid'+`; +`+output_ctx_det+` on the unchanged sibling closes. 4. RIGHT +(`+touches_region+`): locally `+admit+` per-case. + +Variants: - Second-child congruences (`+S_StringConcat_Step2+`, +`+S_Pair_Step2+`): use `+value_context_unchanged+` on the first child +(the value) to align contexts before IH on the second child. - +Single-child congruences (`+S_Inl_Step+`, `+S_Inr_Step+`, +`+S_Copy_Step+`): no sibling, IH directly closes. - `+S_If_Step+`: +condition at `+TBase TBool+` (fixed type), branches at outer `+T+` — +fully constrained. - `+S_StringLen_Step+`: vacuous via inversion chain +(T_StringLen → T_Borrow / T_Borrow_Val: the inner must be `+EVar+` or a +value; neither steps). + +*Open (6 of original 8)* — blocked on *type-alignment circularity +WITHOUT independent-context sibling*: + +S_App_Step1 and S_App_Step2 closed via the "`sibling type_determinacy`" +trick (their sibling’s context — `+Gmid+` for Step1, `+G+` for Step2 via +value_context_unchanged — is independent of the unconstrained `+T1+`). + +For the remaining cases, no such sibling exists or its context depends +on the unconstrained type: + +[cols=",",options="header",] +|=== +|Step rule |Inner-type that’s NOT fixed by outer T +|`+S_Let_Step+` |`+T_Let+`’s binding type `+T1+` +|`+S_LetLin_Step+` |`+T_LetLin+`’s binding type `+T1+` +|`+S_Case_Step+` |`+T_Case+`’s scrutinee `+TSum T1 T2+` +|`+S_Drop_Step+` |`+T_Drop+`’s arg type `+T+` (outer is `+TBase TUnit+`) +|`+S_Fst_Step+` |`+T_Fst+`’s second component `+T2+` +|`+S_Snd_Step+` |`+T_Snd+`’s first component `+T1+` +|=== + +For each: T_X inversion of Hte and Hte’ produces independent fresh +type-vars for the unconstrained inner types. To apply Lemma B’s IH on +the inner step, we’d need to know both typings are at the SAME inner +type — but establishing that requires preservation. Circular. + +*Resolution paths*: 1. Prove Lemma B and preservation by *simultaneous +mutual induction* (restructure both proofs). 2. *Re-state Lemma B* with +a conclusion that doesn’t need shared T (weaker +output-context-equivalence). 3. Add a *type-preservation-under-step* +sub-lemma (essentially the type-only part of preservation) and prove it +separately via a more restricted induction. + +===== Cluster C — region / compound-value ✅ FULLY CLOSED (2026-05-24) + +*Closed*: `+S_Fst+`, `+S_Snd+`, `+S_Copy+`, `+S_Region_Exit+`, +`+S_StringLen+` (atomic), `+S_Region_Enter+`. All 6. + +Recipes: - `+S_Fst+`, `+S_Snd+`, `+S_Copy+`, `+S_Region_Exit+`: invert +the compound rule (T_Fst → T_Pair, T_Snd → T_Pair, T_Copy, +T_Region_Active), apply `+value_context_unchanged+` on each value-typing +premise to align intermediate contexts with G, then `+reflexivity+` +closes. - `+S_StringLen+` atomic: 3-level inversion chain T_StringLen → +T_Borrow_Val (T_Borrow’s EVar form contradicted by ELoc) → T_Loc. Each +preserves the context, so Ga = G. T_I32 on Hte’ gives Gb = G. - +`+S_Region_Enter+`: 4 inversion sub-goals from T_Region / +T_Region_Active × Hte / Hte’. Two contradiction patterns: `+tauto+` +closes Hn-vs-`+In r R0+` cases; explicit `+apply H; left; reflexivity+` +closes the `+~ In r (r :: R0)+` case. The valid sub-goal (Hte = +T_Region, Hte’ = T_Region_Active) closes via `+output_ctx_det+` on the +two inner typings of `+e+` at `+(r :: R0); G+`. + +[width="100%",cols="43%,57%",options="header",] +|=== +|Step rule |What’s needed +|`+S_Region_Enter+` |`+T_Region+` ↔ `+T_Region_Active+` dispatch via +`+In r R+`; needs `+output_ctx_det+` sub-lemma on `+e_inner+` + +|`+S_Region_Exit+` |needs `+region_shrink_preserves_typing+` (already +Qed) + value-output-context invariance + +|`+S_Region_Step+` |*blocked on Phase 3* — region-env weakening for +non-values + +|`+S_StringLen+` (atomic!) |inversion of `+T_StringLen+` → +`+T_Borrow_Val+` → `+T_Loc+` chain needs explicit nested invocation; my +repeat-match doesn’t cover the `+EBorrow (ELoc _ _)+` shape + +|`+S_Copy+` |atomic but compound: `+T_Copy+` outputs `+G'+` where `+G'+` +is value-input; needs `+value_context_unchanged+` invocation + +|`+S_Fst+` / `+S_Snd+` |atomic but inversion of `+T_Pair+` premise needs +`+value_context_unchanged+` to align inner v1/v2 typings +|=== + +==== Effort revision (current — 2026-05-26) + +The earlier "`8-15 focused hours for Lemma B alone`" estimate (recorded +2026-05-24) was keyed off a "`31 remaining cases`" framing. Subsequent +work on the same day closed Clusters A and C entirely, and most of +Cluster B, leaving the shape below. + +Empirical `+coqc 8.18.0+` verification (2026-05-26): + +* `+step_preserves_type+`: *1 open admit* at `+Semantics.v:4885+` +* `+step_output_context_eq+` (Lemma B): *1 open admit* at +`+Semantics.v:5963+` +* `+preservation+`: 12 cascading goals (the `+S_*_Step+` congruence +cases + `+S_Region_Step+`) — these close mechanically once Lemma B is +`+Qed.+`; they are NOT independently hard. + +The two upstream admits are the SAME structural sub-case +(S_Region_Step’s `+r = r1+` "`inner step exits the outer region from +inside`") mirrored across both lemmas. + +*Revised estimate: 4-6 hours wall-clock* for the whole chain to `+Qed+`: + +* ~3h: Option 2 helper lemma body +(`+exit_implies_typing_at_remove_first+`, ~150 LOC by structural +recursion on `+Hstep+`). +* ~1h: plug helper into the two upstream admits. +* ~2h: Phase 2 cascade through `+preservation+`’s 12 congruence goals +(most fall through the existing `+all: try (...)+` chain once Lemma B is +`+Qed.+`). +* ~1h: unwind checklist (PROOF-NEEDS.md, ROADMAP.adoc, +RUST-SPARK-STANCE.adoc, delete this file, `+Admitted → Qed+`). + +==== Effort revision (historical — 2026-05-24) + +{empty}[Retained for context; superseded by the section above.] + +* 4 trivial cases closed by a uniform tactic in ~30 minutes. +* Each of the remaining 31 needs a hand-rolled per-case tactic block. +* Cluster A (7) needs one shared sub-lemma first +(`+subst_preserves_typing_strong+`). +* Cluster B (17–18) shares the same recipe but each case names different +premises and constructor arguments — call it ~10 minutes per case once +the recipe is debugged on the first one. +* Cluster C (6) is a mixed bag; `+S_Region_Step+` carries Phase 3 risk. + +Original estimate: *8–15 focused hours* for Lemma B alone. Cluster A and +Cluster C closed by 2026-05-24 night; most of Cluster B by the same +session. By 2026-05-26 the surface had collapsed to the single shared +S_Region_Step admit. + +==== Watch for: circularity risk + +Some Cluster B cases require knowing that `+e+` and `+e'+` have the same +type before applying the IH. `+type_determinacy+` operates on +same-expression pairs, not stepped pairs. The natural lemma — "`step +preserves type`" — is part of what preservation itself proves. If the +inductive structure of Lemma B turns out to need preservation as a +sub-lemma, the closure path needs revision. Watch for this when +attacking the first Cluster B case. + +=== Open: region-env weakening for non-values (2026-05-26) + +After `+step_preserves_type+` and `+step_output_context_eq+` reached Qed +(via the at-pre helper pattern, Path 3 above), preservation’s 12 +remaining admits split into two flavours: + +[width="100%",cols="40%,>26%,34%",options="header",] +|=== +|Sub-case |Count |Blocker +|Congruence RIGHT (touches_region) |11 |r-shrunk env, sibling might +reference r + +|`+S_Region_Step+` + `+T_Region_Active+` × `+T_Region+` (r=r1) |1 |same +shape inside `+step_preserves_type_at_pre+` +|=== + +The 11 RIGHT-branch admits each have shape: + +____ +`+Hstep : (mu, R, e1) -->> (mu', R', e1')+`, +`+HTR : touches_region e1+`, `+H1 : R; G |- e1 : T1 -| Gmid+`, +`+H2 : R; |- e2 : T2 -| +` (the unchanged sibling), +`+IH : ... → exists G_out, R'; G |- e1' : T1 -| G_out+` + +⊢ `+exists G_out, R'; G |- COMPOUND e1' e2 : ... -| G_out+` +____ + +The IH gives the stepped child at `+R'+`. The sibling `+e2+` is still +typed at `+R+`. To reconstruct `+COMPOUND e1' e2+` at `+R'+`, we need +`+R'; |- e2 : T2 -| +` — i.e. a _non-value_ region-env +weakening lemma carrying e2 from `+R+` to `+R'+`. + +Three candidate formulations were explored in 2026-05-26 sessions: + +==== III-a — Permutation-aware variant (PROVABLE, LANDED, CLOSES 0/12) + +*Statement*: + +[source,coq] +---- +Lemma region_shrink_preserves_typing_dup : + forall R G e T G' r, + R; G |- e : T -| G' -> + In r (remove_first r R) -> + (remove_first r R); G |- e : T -| G'. +---- + +*Proof*: pure transport via `+region_env_perm_typing+` — when +`+In r (remove_first r R)+`, the membership of every region is +preserved. ~15 LOC. Now Qed at `+Semantics.v:3332+`. + +*Why it doesn’t close any admit*: the duplicate-r hypothesis +`+In r (remove_first r R)+` is never available in the touches_region +RIGHT branches. `+S_Region_Exit+`’s premise (`+In r R+`) only guarantees +single membership; the dual `+In r (remove_first r R)+` would require +multi-occurrence, which a well-typed initial program never produces +(`+T_Region+`’s `+~In r R+` premise prevents same-name shadowing +throughout reachable configurations). + +Lands as adjunct infrastructure: completes the case split alongside +`+region_shrink_preserves_typing+` and documents the explored +formulation. + +==== III-b — `+no_leaks+` invariant as oracle (NOT VIABLE) + +`+no_leaks+` (line 374) proves only a memory-side fact: +`+mem_read mu' l = Some (CString r s) -> False+`. It says nothing about +whether `+e'+` is syntactically free of `+r+`. The bridge that would +make it useful — `+step_exit_implies_free_of_exited_region+` — is +*false* for congruence cases (the surviving sibling’s references to r +break the freedom claim, as documented above at +"`step_exit_implies_free_of_exited_region would be false`"). +Re-engineering `+no_leaks+` to produce expression-side freedom would +require strengthening its conclusion from "`memory free of r`" to +"`resulting expression syntactically free of r`" — a different judgement +entirely, equivalent in effort to the auxiliary lemma itself. III-b +collapses to III-a / Option 2. + +==== III-c — Restated preservation with membership-equivalence (WRONG AXIS) + +Weakening preservation’s conclusion to +`+exists R'', (forall r, In r R'' <-> In r R') /\ R''; G |- e' : T -| G'+` +does not help: 11 of the 12 open goals are blocked on *G-context* Skolem +alignment (already solved by the Swarm-C `+step_output_context_eq+` +splice), and the 12th wants genuine _weakening_ (`+r :: R'+`), not +membership- equivalence. III-c diagnoses the wrong axis. + +==== Conclusion + +The 12 admits do *not* dissolve under a clean lemma-level fix. They +require one of: + +[arabic] +. *Mutual induction* with `+preservation+` and `+step_preserves_type+` +simultaneously (Option 1 above — the standard textbook approach for +region calculi, but a significant restructuring). +. *Inversion-on-Hstep structural recursion* (~150 LOC, Option 2 above) — +orthogonal to the current case split; closes the single +`+step_preserves_type_at_pre+` admit at the source, which cascades to +the corresponding preservation admit. +. *Type-system change*: add a sibling-region-disjointness side condition +to `+T_Let+`, `+T_App+`, etc. — ensures every well-typed compound rules +out the bad configuration where exit-able r is shared with a sibling. +Requires re-proving every existing lemma under the new typing rule. + +Recommend Option 2 (~150 LOC, scope-isolated) over Option 1 (refactor +entire proof). Type-system change (Option 3) is the "`correct`" fix for +the underlying concrete-name encoding gap, but the cost (re-prove every +lemma) is high. + +=== Unwind checklist (when finally closed) + +[arabic] +. Replace `+Admitted.+` with `+Qed.+` +. Flip `+ROADMAP.adoc+`’s admitted-proofs counter `+1 → 0+` +. Flip `+PROOF-NEEDS.md+`’s status row + delete the "`what needs +proving`" item for `+preservation+` +. Delete this file +. Update `+RUST-SPARK-STANCE.adoc+`’s E1 row from OWED to DISCHARGED +(and remove the "`honest gap`" entry about preservation) +. Delete the proof-status comment block at `+Semantics.v+` immediately +below the (now-`+Qed.+`) preservation diff --git a/formal/PRESERVATION-HANDOFF.md b/formal/PRESERVATION-HANDOFF.md deleted file mode 100644 index cc64588a..00000000 --- a/formal/PRESERVATION-HANDOFF.md +++ /dev/null @@ -1,659 +0,0 @@ - -Copyright (c) Jonathan D.A. Jewell ---> - -# Hand-off: closing `preservation` in `formal/Semantics.v` - -> # 🛑 ARCHAEOLOGY ONLY — do NOT follow the closure plans in this file -> -> The verified counterexample at `formal/Counterexample.v` (five -> lemmas `Qed.`) shows `Theorem preservation` in `formal/Semantics.v` -> is **provably false**. The reduction-from-910-goals story below is -> the **historical record** of attempts that, at the time, looked -> like they would close it. None of them work — preservation -> cannot be closed because no proof exists. -> -> **DO NOT** spend session time on: -> - The 5-phase "Preservation closure plan" (Lemma B / region-weakening -> / Phase 5 Qed flip) — that section has been removed from -> `ROADMAP.adoc` as of 2026-05-27. -> - Continuing the 12 → 0 reduction story. -> - Extending the legacy `Semantics.v` with new lemmas to support -> any of the above (`step_preserves_type_at_pre`, -> `step_output_context_eq_at_pre`, etc. are all in this class). -> -> The canonical design for the fix is the **four-layer redesign** in -> `formal/PRESERVATION-DESIGN.md`. Preservation is re-derived -> per-layer (`preservation_l1` in `Semantics_L1.v`, etc.), not -> closed against the legacy judgment. The owner directive -> (2026-05-27) is in `CLAUDE.md` at the repo root. -> -> This file is retained so the per-case diagnostic data (which -> congruence patterns hit which obstacle, etc.) remains available -> to inform the layered design — but reading it as a closure plan -> is the trap. - -Diagnostic + remediation log. The proof is still `Admitted.`, but as -of **2026-05-21** it's **down from 910 open goals to 12** via four -landed PRs. This file tells whoever picks it up next exactly what's -open and what the canonical closure path is. - -> **The canonical closure plan is now in `formal/PRESERVATION-DESIGN.md`** -> (four-layer redesign; L1 region capability threading). The prior -> "Preservation closure plan" in `ROADMAP.adoc` is superseded; this -> file remains as the per-case diagnostic record. Read the design -> doc first; come back here for case detail and the historical -> closure-attempt log. - -## State at a glance - -| Date | Open goals | Notes | -|------|-----------:|-------| -| 2026-04-27 | "fully closed" | In-file comment — but `coqc` rejected the `Qed.`. The claim was unsubstantiated; the proof never closed. | -| 2026-05-20 (am) | 910 | Discovered via `Show. Show Existentials.` before the `Admitted.`. Exactly 35 (step rules) × 26 (typing rules). The existing `try solve [...]` chain closes ZERO. | -| 2026-05-20 (pm) | 29 | After the standard preservation pattern (`remember (mu, R, e) as cfg` + symmetric for cfg', then `inversion Hcfg; subst; inversion Hcfg'; subst;` inside each case). 97% reduction. PR #102. | -| 2026-05-20 (eve) | 22 | After `revert mu R e mu' R' e' Hcfg Hcfg'` before `induction Hstep` so each case's IH carries universal quantification over the inner step's config. PR #106. | -| 2026-05-20 (eve) | 22 | Region-invariance lemma `step_R_eq_or_touches_region` landed as infrastructure (no goal closures). PR #114. | -| 2026-05-20 (night) | **12** | 10 β-reduction / value-step cases discharged via per-case manual proofs using the lemma. PR #116. **98.7% reduction across one day.** | -| 2026-05-24 | Lemma B 4/35 closed | `step_output_context_eq` scaffolded with `cfg`-remember pattern. Atomic-axiom tactic closes 4 step rules. PRs #121/#124/#126. | -| 2026-05-24 (late) | Lemma B 31/35 closed | Cluster A (β-reduction, 7) **FULLY CLOSED** via `subst_preserves_typing_strong` + `output_ctx_det`. Cluster C (region/compound-value, 6) **FULLY CLOSED** via inversion + `value_context_unchanged`. Cluster B (congruence) 9 of 18 closed via R-shape dispatch. | -| 2026-05-26 | **1 + 1 + 12** | Empirical `coqc 8.18.0` re-verification: 1 admit in `step_preserves_type` (Semantics.v:4885), 1 admit in `step_output_context_eq` (Semantics.v:5963), 12 cascading goals in `preservation`. The two upstream admits are the SAME structural sub-case — S_Region_Step's `r = r1` "exited from inside" — mirrored across both lemmas. | -| 2026-05-26 (eve) | **0 + 0 + 12** (Qed × 2) | **Path 3 (at-pre helper) lands.** Both upstream lemmas are now `Qed`. Introduced two NEW helper lemmas (`step_preserves_type_at_pre` and `step_output_context_eq_at_pre`) whose typings are at the SHARED pre-step env R. The S_Region_Step cross-case `T_Region_Active × T_Region` collapses to a contradiction (In r R vs ~In r R at same env) — sidestepping the original obstacle. Plug-in via `region_env_perm_typing` + `remove_first_then_cons_membership_eq` (existing, Qed). The 12 cascading goals in `preservation` remain — they're a SEPARATE structural problem, not the shared admit. | -| 2026-05-26 (late eve) | **Qed × 4 + 11** | **Swarm A + B + C complete.** `step_preserves_type_at_pre` and `step_output_context_eq_at_pre` are now Qed (Swarm A + B per-goal closures landed). Swarm C oracle splice closes all 10 congruence LEFT branches in `preservation` via `step_output_context_eq` (Qed) — `assert Gmid = Gout by (eapply step_output_context_eq; …); subst` unifies the IH's existential output context with the sibling's Gmid. `S_StringLen_Step` closes ENTIRELY as vacuous (`EBorrow`'s inner is `EVar` or value, neither steps — pattern lifted from `S_Borrow_Step` elsewhere in this proof). 11 admits remain in `preservation`: 10 RIGHT-only sub-cases (`HTR : touches_region` in scope) + 1 `S_Region_Step` (special). All 10 RIGHT cases share the same shape and block on the same region-env weakening lemma for non-values (= Brief C, structural follow-up). | -| 2026-05-26 (night) | **Qed × 4 + 11** (narrowed) | **MIDDLE narrowing lands.** Converted all 10 congruence `S_*_Step` blocks from 2-way (`step_R_eq_or_touches_region`) to 3-way (`step_R_change_shape`) dispatch. LEFT branch unchanged. **MIDDLE (R' = rw :: R, push)** now closes via the same `step_output_context_eq` oracle on the stepped child + `region_add_typing` to lift the sibling typing from `R` to `rw :: R`. RIGHT (`R' = remove_first r R`, pop) remains `idtac`. Result: each of the 10 admits is now narrower — `Hrem : R0' = remove_first r R0` and `HinR : In r R0` are in scope (vs the broader `HTR : touches_region` before). Brief C's required lemma reduces from "region-env weakening across any touches_region step" to "pop-only weakening" (the surviving sibling must be transportable from `R` to `remove_first r R` when `In r R`). Coq subtlety: the 2 S_StringConcat blocks must use `[rw …]` instead of `[r …]` in the destruct pattern because the outer T_StringConcat inversion binds an `r` (the TString region) in scope already. | - -> **Path 3 (landed 2026-05-26 eve):** The Option 2 plan (structural -> recursion deriving `expr_free_of_region`) was **blocked** by the -> `ELet (ERegion r v_inner) (ELoc l r)` counterexample (sibling -> references to r survive the exit, so `expr_free_of_region r e'` is -> false in general). Path 1 (mutual induction) would have been a -> heavy refactor (~8-12h). -> -> Path 3 introduces TWO helper lemmas whose signatures match the -> upstream pair except both typings are at the SHARED pre-step env R -> (not R/R'). In this framing, the S_Region_Step cross-case -> `T_Region_Active × T_Region` collapses to a vacuous contradiction -> (`In r R` vs `~In r R` at the same env), sidestepping the obstacle. -> The plug-in then uses the existing `region_env_perm_typing` to -> transport the body's typing from `r :: remove_first r R0` to `R0` -> (membership-equivalent when `In r R0`). Net code change: ~100 -> lines + 2 helper bodies (~1600 lines copy-paste from upstream). -> Wall-clock: **~3h** to land both Qed flips. -> -> **Status of the helpers themselves (2026-05-26 late eve, commit -> `d6ebf68`):** both are now `Qed`. The mass-copied Cluster A/B/C -> tactic blocks (verbatim from the upstream lemmas, patterns use -> `?R`/`?R'` polymorphically) close the bulk; the residual cases that -> didn't fit those patterns are closed by explicit `1: {...}` per- -> goal blocks at the tail of each helper — 5 in -> `step_preserves_type_at_pre` (S_StringConcat_Step2, S_App_Step2, -> S_Snd atomic, S_Case_Step, S_Copy atomic) and 11 in -> `step_output_context_eq_at_pre` (the listed S_*_Step congruence -> cases). The upstream `step_preserves_type` and -> `step_output_context_eq` `Qed` chain is therefore non-axiomatic. -> -> **Status of preservation:** 12 cascading goals remain, expected to -> still need a region-env weakening for non-values (per the original -> analysis below). The at-pre helpers DON'T directly help with these -> — they help with the UPSTREAM lemma obstacle, not preservation's -> own touches_region RIGHT branch. Re-diagnosis is in progress now -> that step_preserves_type + step_output_context_eq are available as -> oracles. - -## What the 910 → 29 fix did - -The prior `induction Hstep` did not substitute the outer expression -slot `e` to the constructor's form — so `inversion Htype` produced -all 26 typing arms per step rule instead of just the diagonal. Cross- -cases (e.g. `S_StringNew` step + `T_Unit` typing) had no -discriminating equation in scope, so `try solve [exfalso; -discriminate | exfalso; congruence]` couldn't fire. - -The fix: - -```coq -intros mu R e mu' R' e' Hstep. -remember (mu, R, e) as cfg eqn:Hcfg. -remember (mu', R', e') as cfg' eqn:Hcfg'. -induction Hstep; intros G0 T0 G0' Htype; - inversion Hcfg; subst; - inversion Hcfg'; subst; - inversion Htype; subst; - (* … existing try-solve chain … *) -``` - -`remember` turns `Hstep : step (mu, R, e) (mu', R', e')` into -`Hstep : step cfg cfg'` with two side equations `Hcfg : ... = (mu, R, -e)`, `Hcfg' : ... = (mu', R', e')`. Now `induction Hstep` substitutes -the constructor's "from" config into `cfg` and the equation `Hcfg` -becomes (for `S_StringNew`) `(mu0, R0, EStringNew r s) = (mu, R, e)`. -`inversion Hcfg; subst` decomposes this and substitutes -`e := EStringNew r s` everywhere — including in `Htype`. Then -`inversion Htype; subst` only generates the diagonal `T_StringNew` -arm; the 25 cross-arms are eliminated by inversion's constructor- -mismatch check. - -The earlier `remember e_typed as e_orig eqn:He_orig` (the original -"preserve discriminating equation" attempt) was a misdiagnosis — it -remembered the *typing's* `e`, which was already abstract; the -problem was that the *config's* expression slot was abstract. - -## How to reproduce the diagnostic - -```coq - end). -Show. (* prints current goal *) -Show Existentials. (* prints all 29 unresolved metavariables *) -Admitted. -``` - -then: - -```sh -cd formal -coq_makefile -f _CoqProject -o Makefile.coq && make -f Makefile.coq -``` - -`coqc` prints goal count + every open existential. Restore the -`Admitted.` afterwards. Same recipe (now yielding 29 instead of 910) -is the per-case work list. - -## The 29 remaining goals (per-case checklist) - -### Axiom cases needing explicit reconstruction (~3 goals) - -| Step rule | Goal needs | Tactic sketch | -|-----------|-----------|---------------| -| `S_StringNew` | type `ELoc l r` at `TString r` | `eexists; eapply T_Loc; eauto using mem_alloc_lookup` | -| `S_StringConcat` | type `ELoc l' r` at `TString r0` | same as above, with the new alloc | -| `S_StringLen` | type `EI32 (String.length s)` at `TBase TI32` | `eexists; constructor` | - -### β-reduction cases needing `subst_preserves_typing` (~6 goals) - -| Step rule | Tactic | -|-----------|--------| -| `S_Let_Val`, `S_LetLin_Val`, `S_App_Fun`, `S_Fst`, `S_Snd`, `S_Case_Inl`, `S_Case_Inr` | `eexists; eapply subst_preserves_typing; eauto` (most should fall through the existing chain once the expression slots are concrete) | -| `S_If_True`, `S_If_False` | `eexists; eassumption` (the branch typing is already in context) | - -### Congruence cases needing IH + reconstruction (~15 goals) - -For each `S_*_Step`: `destruct (IHHstep ...) as [G_out Hout]; eexists; -econstructor; eauto`. The IH's form is awkward after `remember` — -contains the `Hcfg`/`Hcfg'` equations as extra premises. May need -`specialize IHHstep with (1 := Heqcfg_inner) (2 := Heqcfg'_inner)` -first, or restructure to revert + induct on typing instead of step. - -### Region cases (~3 goals) - -| Step rule | Status | -|-----------|--------| -| `S_Region_Enter` | typing reconstruction (`T_Region_Active` + `In r (r::R)`) — the existing `try solve [eexists; eapply T_Region_Active; ...]` should fire | -| `S_Region_Exit` | needs `region_shrink_preserves_typing` (existing — Qed) + the `expr_free_of_region` step premise | -| `S_Region_Step` + `T_Region_Active` | **the bottleneck** — needs a **region-env *weakening* lemma for non-values** that does not yet exist. Roughly: `forall R e G G' T r, R; G \|- e : T -\| G' -> ~ In r R -> e is not a value -> (r :: R); G \|- e : T -\| G'`. The "not a value" precondition is delicate — naive weakening is unsound for `EBorrow` and friends. Probably 50–150 LOC by itself. | - -### Linear cases (~2 goals) - -| Step rule | Tactic | -|-----------|--------| -| `S_Drop` | `eexists; constructor` (drop yields `EUnit : TBase TUnit`) | -| `S_Copy` | `eexists; constructor; assumption` (copy yields `EPair v v : TProd T T`) | - -## What it would take to finish - -Realistic estimate: **days, not weeks** — the 29 remaining goals are -all standard preservation-proof glue except for the region-env -weakening lemma. The lemma's design (which non-value carriers admit -weakening, which don't) is the only genuine theory question. The -mechanical 28 are 1–2 days of tactic work. - -## What is NOT a fix - -- Adding more `try solve [...]` lines to the existing chain at random. - Run `Show. Show Existentials.` first to see what's actually open. -- Replacing `induction Hstep` with `inversion Hstep` — that loses the - IHs needed for congruence cases. -- Mass-`Admitted.` per case — defeats the point and conflicts with - estate's "build is the only oracle" policy. The honest mark is one - `Admitted.` on `preservation`, not 29. - -## `step_preserves_type` per-case status (2026-05-24, late) - -Empirically verified against `coqc 8.18.0`. The 12 cases identified -as "remaining open" after the f499c82 clone-out have now each been -given an explicit per-case proof skeleton. **Three iterations**: - -1. **First pass**: dispatched on `step_R_eq_or_touches_region`, - closed the LEFT (R = R') branches, admitted RIGHT. 8 admits remained. -2. **Second pass**: introduced finer `step_R_change_shape` (3-way - disjunction: equal / prepend r / remove_first r), closed the MIDDLE - (prepend) branch for all 7 congruence cases via `region_add_typing`. - Still 8 admits, but each tied to the strictly-narrower - "remove_first r R" sub-case rather than the broader "touches_region". -3. **Third pass**: added the `remove_first_then_cons_membership_eq` - helper (proving `r :: remove_first r R` and `R` have the same - membership whenever `In r R`, no `NoDup R` required) and used it - with `region_env_perm_typing` to close the RIGHT (`remove_first`) - sub-case in all 7 congruence proofs. **Now 1 admit remains**, - tied to S_Region_Step's exfalso when the inner step exits the - outer region from inside (r = r1 sub-case). - -**Moved** `touches_region` + `step_R_eq_or_touches_region` to before -`step_preserves_type` so the latter can dispatch on the LEFT -(R = R') branch. - -### Fully closed (4 of 12) - -| Goal | Step rule | Closure | -|------|-----------|---------| -| 1 | `S_StringConcat_Step2` | outer T = TString r is structurally constrained; IH on (e2, e2') gives r = r' | -| 8 | `S_Snd` atomic (ESnd (EPair v1 v2) → v2) | `value_context_unchanged` on v1 + `type_determinacy` on v2 | -| 10 | `S_Region_Exit` (ERegion r v → v) | `region_shrink_preserves_typing` bridges R0 and remove_first r R0, then `type_determinacy` | -| 12 | `S_Copy` atomic (ECopy v → EPair v v) | `value_context_unchanged` on first projection + two `type_determinacy` calls on v | - -### Fully closed via R-shape dispatch (7 of 12 congruence cases) - -After the third pass, every congruence case closes via the 3-way -dispatch on `step_R_change_shape`: - -| Goal | Step rule | LEFT (R = R') | MIDDLE (R' = r :: R) | RIGHT (R' = remove_first r R) | -|------|-----------|----------------|----------------------|-------------------------------| -| 2 | `S_Let_Step` | ✅ | ✅ via `region_add_typing` | ✅ via lift+perm | -| 3 | `S_LetLin_Step` | ✅ | ✅ | ✅ | -| 4 | `S_App_Step2` | ✅ | ✅ | ✅ | -| 5 | `S_If_Step` | ✅ | ✅ | ✅ | -| 6 | `S_Pair_Step1` | ✅ | ✅ | ✅ | -| 7 | `S_Pair_Step2` | ✅ | ✅ | ✅ | -| 9 | `S_Case_Step` | ✅ | ✅ | ✅ | - -The RIGHT sub-case (when the inner step exits a region) is closed by: -1. `region_add_typing` lifts the post-step sibling typing from - `remove_first r R0` to `r :: remove_first r R0`. -2. `region_env_perm_typing` converts to `R0` via - `remove_first_then_cons_membership_eq` — the new helper that proves - `r :: remove_first r R0` and `R0` have the same membership when - `In r R0` (NO `NoDup R` invariant needed; works even for duplicates). -3. `type_determinacy` aligns the types under the now-shared `R0`. - -### Partially closed — one sub-case admitted (1 of 12) - -| Goal | Step rule | Closures | Remaining admit | -|------|-----------|----------|-----------------| -| 11 | `S_Region_Step` (ERegion r e → ERegion r e') | T_Region (Hte) contradicted by `In r R0`; T_Region_Active × T_Region_Active via IH; T_Region_Active × T_Region 3 of 4 R-shape sub-cases close (R = R', R' = r1::R0, R' = remove_first r1 R0 with r ≠ r1) — all by contradiction with `~In r R0'` | One sub-case: T_Region_Active × T_Region with `R0' = remove_first r R0` (the outer r is exited from inside). Closing requires `expr_free_of_region r e'` to shrink the post-step inner typing, derivable but requires further inversion on `Hstep` to reach the underlying `S_Region_Exit`'s premise. | - -### Net effect - -- **Before**: `step_preserves_type` was `all: admit` with 12 open - goals — every case admitted with no structure. -- **After (pass 1)**: 4 of 12 closed fully, 7 of 12 had LEFT (R = R') - branch closed, 1 of 12 had main branch closed. 8 admits. -- **After (pass 2)**: each congruence admit narrowed to just the - `R' = remove_first r R` sub-case via `step_R_change_shape` + - `region_add_typing`. Still 8 admits but each strictly narrower. -- **After (pass 3)**: all 7 congruence RIGHT sub-cases closed via the - new `remove_first_then_cons_membership_eq` helper + - `region_env_perm_typing`. **1 admit remains** — the - `T_Region_Active × T_Region` cross-case of `S_Region_Step` when - the inner step exits the outer region from inside. - -New supporting lemmas added (all `Qed.`): -- `step_R_change_shape` (~10 LOC): refines the 2-way disjunction - into 3-way, also exposing `~In r R` / `In r R` for the prepend / - remove cases. -- `remove_first_then_cons_membership_eq` (~15 LOC): proves - `(r :: remove_first r R)` and `R` have the same membership when - `In r R`. NO `NoDup R` required. - -The single remaining admit is the cross-case where `Hte` uses -`T_Region_Active` and `Hte'` uses `T_Region` — meaning the inner -step exits the outer `ERegion`'s own region `r`, and `r` was unique -in `R0` (so post-step `~In r remove_first r R0`). - -**The genuine obstacle:** `e'` may syntactically reference `r` even -though `r` was unique pre-step. Concrete witness: if -`e = ELet (ERegion r v_inner) (ELoc l r)`, then after the inner -`S_Region_Exit` we get `e' = ELet v_inner (ELoc l r)`. The post-step -sibling `ELoc l r` still references `r`, so `expr_free_of_region r e'` -is false. Yet `e'` is well-typed under `r :: remove_first r R0` -because `T_Region` re-introduces `r` at the head, making -`ELoc l r` typeable via the freshly-bound `r`. This is the -semantic-freshness issue inherent to concrete-name region encodings: -with alpha-renaming the post-step `r` would be a distinct region -name; with concrete names the syntactic occurrence persists. - -Consequences for closure paths: -- Adding `NoDup R` as an invariant does **not** close it. The - `T_Region`-vs-`T_Region_Active` choice in `Hte'` already encodes - uniqueness in scope (`H3 : ~In r remove_first r R0` plus - `H : In r R0` implies `r` unique). NoDup gives no extra info. -- A `typing_implies_free_of_absent_region` lemma doesn't apply - because `e'` is typed at `r :: remove_first r R0` where `r` IS - present (at the head), so `~In r R` doesn't hold for the typing - in scope. -- A `step_exit_implies_free_of_exited_region` lemma would be **false** - for congruence cases that preserve siblings — the sibling's - surviving `r`-references break the freedom claim. - -The genuinely-closing options are: -1. **Mutual recursion with `preservation`**: prove `preservation` - and `step_preserves_type` simultaneously. `preservation`'s direct - construction of a typing for `e'` at the post-step `R'` provides - exactly what this admit needs. Standard textbook approach for - region calculi, but a significant restructuring touching both proofs. -2. **Inversion on `Hstep` with structural recursion**: directly case-split - on the step rule path that produced `R' = remove_first r R0`, - handling the `S_Region_Exit`-at-top sub-case via region_shrink - (works because `e' = v`, free of `r` by `S_Region_Exit`'s premise), - and handling the congruence-bubbling sub-cases by recursive structural - argument on the wrapping. ~150 LOC, orthogonal to the current case split. - -Both are substantial follow-ups. The current single admit is bounded -and well-documented; closing it should be deferred to whoever takes -on (1) or (2) as a focused effort. - -## Lemma B per-case status (2026-05-24) - -Empirically verified against `coqc 8.18.0`. The Phase 1 scaffold for -`step_output_context_eq` now uses the `cfg`-remember pattern that -mirrors `step_R_eq_or_touches_region` and `preservation`, plus an -atomic-axiom closure tactic. **4 of 35 step rules close**; 31 remain. - -### Closed (4) - -| Step rule | Why it closes | -|-----------|---------------| -| `S_StringNew` | atomic: `EStringNew → ELoc`, both type to identity-output | -| `S_StringConcat` | atomic: `EStringConcat (ELoc _) (ELoc _) → ELoc`, all premises invert to identity-output T_Loc | -| `S_Drop` | atomic: `EDrop (ELoc _) → EUnit`, both T_Drop and T_Unit are identity-output | -| `S_Borrow_Step` | **accidental congruence closure**: both `T_Borrow` and `T_Borrow_Val` output the input context unchanged, so `Ga = G = Gb` regardless of whether the inner step is reachable. Vacuous-but-closes. | - -### Open (24, was 31) - -#### Cluster A — β-reduction ✅ FULLY CLOSED (2026-05-24) - -All 7 β-reduction cases closed via -`subst_preserves_typing_strong` (PR: this branch) + `output_ctx_det` -(PR: this branch). Recipe per case: -1. Invert the outer compound typing (`T_Let`, `T_App`, `T_If`, - `T_Case`) to expose body + value premises. -2. For T_App: also invert `T_Lam` on the function value. -3. For T_Case: apply `value_context_unchanged` on the EInl/EInr - premise, then invert `T_Inl`/`T_Inr`. -4. Apply `value_context_unchanged` on the value premise(s) to - align intermediate contexts with the input context. -5. `destruct (subst_preserves_typing_strong ...)` to construct a - typing of the substituted form at the specific output context. -6. `eapply output_ctx_det` against `Htype_e'` to conclude - `Ga = Gb`. - -Closed cases: `S_Let_Val`, `S_LetLin_Val`, `S_App_Fun`, -`S_If_True`, `S_If_False`, `S_Case_Inl`, `S_Case_Inr`. - -#### Cluster B — congruence (10 of 18 closed, 8 open) - -**Closed (2026-05-24)**: `S_StringConcat_Step1`, -`S_StringConcat_Step2`, `S_Pair_Step1`, `S_Pair_Step2`, -`S_Inl_Step`, `S_Inr_Step`, `S_Copy_Step`, `S_If_Step`, -`S_StringLen_Step`. (Plus `S_Borrow_Step` closed accidentally -earlier.) - -Recipe (canonical two-child congruence, e.g. -`S_StringConcat_Step1`): -1. Invert both `Hte` and `Hte'`. -2. `pose proof step_R_eq_or_touches_region` to dispatch `R = R'`. -3. LEFT (R = R'): apply IH on inner step's typings to get - `Gmid = Gmid'`; `output_ctx_det` on the unchanged sibling - closes. -4. RIGHT (`touches_region`): locally `admit` per-case. - -Variants: -- Second-child congruences (`S_StringConcat_Step2`, - `S_Pair_Step2`): use `value_context_unchanged` on the first - child (the value) to align contexts before IH on the second - child. -- Single-child congruences (`S_Inl_Step`, `S_Inr_Step`, - `S_Copy_Step`): no sibling, IH directly closes. -- `S_If_Step`: condition at `TBase TBool` (fixed type), branches - at outer `T` — fully constrained. -- `S_StringLen_Step`: vacuous via inversion chain (T_StringLen → - T_Borrow / T_Borrow_Val: the inner must be `EVar` or a value; - neither steps). - -**Open (6 of original 8)** — blocked on **type-alignment circularity WITHOUT independent-context sibling**: - -S_App_Step1 and S_App_Step2 closed via the "sibling type_determinacy" -trick (their sibling's context — `Gmid` for Step1, `G` for Step2 via -value_context_unchanged — is independent of the unconstrained `T1`). - -For the remaining cases, no such sibling exists or its context -depends on the unconstrained type: - -| Step rule | Inner-type that's NOT fixed by outer T | -|-----------|----------------------------------------| -| `S_Let_Step` | `T_Let`'s binding type `T1` | -| `S_LetLin_Step` | `T_LetLin`'s binding type `T1` | -| `S_Case_Step` | `T_Case`'s scrutinee `TSum T1 T2` | -| `S_Drop_Step` | `T_Drop`'s arg type `T` (outer is `TBase TUnit`) | -| `S_Fst_Step` | `T_Fst`'s second component `T2` | -| `S_Snd_Step` | `T_Snd`'s first component `T1` | - -For each: T_X inversion of Hte and Hte' produces independent -fresh type-vars for the unconstrained inner types. To apply -Lemma B's IH on the inner step, we'd need to know both typings -are at the SAME inner type — but establishing that requires -preservation. Circular. - -**Resolution paths**: -1. Prove Lemma B and preservation by **simultaneous mutual - induction** (restructure both proofs). -2. **Re-state Lemma B** with a conclusion that doesn't need - shared T (weaker output-context-equivalence). -3. Add a **type-preservation-under-step** sub-lemma (essentially - the type-only part of preservation) and prove it separately - via a more restricted induction. - -#### Cluster C — region / compound-value ✅ FULLY CLOSED (2026-05-24) - -**Closed**: `S_Fst`, `S_Snd`, `S_Copy`, `S_Region_Exit`, -`S_StringLen` (atomic), `S_Region_Enter`. All 6. - -Recipes: -- `S_Fst`, `S_Snd`, `S_Copy`, `S_Region_Exit`: invert the compound - rule (T_Fst → T_Pair, T_Snd → T_Pair, T_Copy, T_Region_Active), - apply `value_context_unchanged` on each value-typing premise to - align intermediate contexts with G, then `reflexivity` closes. -- `S_StringLen` atomic: 3-level inversion chain T_StringLen → - T_Borrow_Val (T_Borrow's EVar form contradicted by ELoc) → T_Loc. - Each preserves the context, so Ga = G. T_I32 on Hte' gives Gb = G. -- `S_Region_Enter`: 4 inversion sub-goals from T_Region / - T_Region_Active × Hte / Hte'. Two contradiction patterns: - `tauto` closes Hn-vs-`In r R0` cases; explicit - `apply H; left; reflexivity` closes the `~ In r (r :: R0)` - case. The valid sub-goal (Hte = T_Region, Hte' = T_Region_Active) - closes via `output_ctx_det` on the two inner typings of `e` at - `(r :: R0); G`. - - - -| Step rule | What's needed | -|-----------|---------------| -| `S_Region_Enter` | `T_Region` ↔ `T_Region_Active` dispatch via `In r R`; needs `output_ctx_det` sub-lemma on `e_inner` | -| `S_Region_Exit` | needs `region_shrink_preserves_typing` (already Qed) + value-output-context invariance | -| `S_Region_Step` | **blocked on Phase 3** — region-env weakening for non-values | -| `S_StringLen` (atomic!) | inversion of `T_StringLen` → `T_Borrow_Val` → `T_Loc` chain needs explicit nested invocation; my repeat-match doesn't cover the `EBorrow (ELoc _ _)` shape | -| `S_Copy` | atomic but compound: `T_Copy` outputs `G'` where `G'` is value-input; needs `value_context_unchanged` invocation | -| `S_Fst` / `S_Snd` | atomic but inversion of `T_Pair` premise needs `value_context_unchanged` to align inner v1/v2 typings | - -### Effort revision (current — 2026-05-26) - -The earlier "8-15 focused hours for Lemma B alone" estimate (recorded -2026-05-24) was keyed off a "31 remaining cases" framing. Subsequent -work on the same day closed Clusters A and C entirely, and most of -Cluster B, leaving the shape below. - -Empirical `coqc 8.18.0` verification (2026-05-26): - -- `step_preserves_type`: **1 open admit** at `Semantics.v:4885` -- `step_output_context_eq` (Lemma B): **1 open admit** at `Semantics.v:5963` -- `preservation`: 12 cascading goals (the `S_*_Step` congruence - cases + `S_Region_Step`) — these close mechanically once Lemma B is - `Qed.`; they are NOT independently hard. - -The two upstream admits are the SAME structural sub-case (S_Region_Step's -`r = r1` "inner step exits the outer region from inside") mirrored -across both lemmas. - -**Revised estimate: 4-6 hours wall-clock** for the whole chain to `Qed`: - -- ~3h: Option 2 helper lemma body - (`exit_implies_typing_at_remove_first`, ~150 LOC by structural - recursion on `Hstep`). -- ~1h: plug helper into the two upstream admits. -- ~2h: Phase 2 cascade through `preservation`'s 12 congruence goals - (most fall through the existing `all: try (...)` chain once Lemma B - is `Qed.`). -- ~1h: unwind checklist (PROOF-NEEDS.md, ROADMAP.adoc, - RUST-SPARK-STANCE.adoc, delete this file, `Admitted → Qed`). - -### Effort revision (historical — 2026-05-24) - -[Retained for context; superseded by the section above.] - -- 4 trivial cases closed by a uniform tactic in ~30 minutes. -- Each of the remaining 31 needs a hand-rolled per-case tactic block. -- Cluster A (7) needs one shared sub-lemma first - (`subst_preserves_typing_strong`). -- Cluster B (17–18) shares the same recipe but each case names - different premises and constructor arguments — call it ~10 minutes - per case once the recipe is debugged on the first one. -- Cluster C (6) is a mixed bag; `S_Region_Step` carries Phase 3 risk. - -Original estimate: **8–15 focused hours** for Lemma B alone. Cluster A -and Cluster C closed by 2026-05-24 night; most of Cluster B by the same -session. By 2026-05-26 the surface had collapsed to the single shared -S_Region_Step admit. - -### Watch for: circularity risk - -Some Cluster B cases require knowing that `e` and `e'` have the -same type before applying the IH. `type_determinacy` operates on -same-expression pairs, not stepped pairs. The natural lemma — -"step preserves type" — is part of what preservation itself -proves. If the inductive structure of Lemma B turns out to need -preservation as a sub-lemma, the closure path needs revision. -Watch for this when attacking the first Cluster B case. - -## Open: region-env weakening for non-values (2026-05-26) - -After `step_preserves_type` and `step_output_context_eq` reached Qed -(via the at-pre helper pattern, Path 3 above), preservation's 12 -remaining admits split into two flavours: - -| Sub-case | Count | Blocker | -|----------|------:|---------| -| Congruence RIGHT (touches_region) | 11 | r-shrunk env, sibling might reference r | -| `S_Region_Step` + `T_Region_Active` × `T_Region` (r=r1) | 1 | same shape inside `step_preserves_type_at_pre` | - -The 11 RIGHT-branch admits each have shape: - -> `Hstep : (mu, R, e1) -->> (mu', R', e1')`, -> `HTR : touches_region e1`, -> `H1 : R; G |- e1 : T1 -| Gmid`, -> `H2 : R; |- e2 : T2 -| ` (the unchanged sibling), -> `IH : ... → exists G_out, R'; G |- e1' : T1 -| G_out` -> -> ⊢ `exists G_out, R'; G |- COMPOUND e1' e2 : ... -| G_out` - -The IH gives the stepped child at `R'`. The sibling `e2` is still -typed at `R`. To reconstruct `COMPOUND e1' e2` at `R'`, we need -`R'; |- e2 : T2 -| ` — i.e. a *non-value* region-env -weakening lemma carrying e2 from `R` to `R'`. - -Three candidate formulations were explored in 2026-05-26 sessions: - -### III-a — Permutation-aware variant (PROVABLE, LANDED, CLOSES 0/12) - -**Statement**: -```coq -Lemma region_shrink_preserves_typing_dup : - forall R G e T G' r, - R; G |- e : T -| G' -> - In r (remove_first r R) -> - (remove_first r R); G |- e : T -| G'. -``` - -**Proof**: pure transport via `region_env_perm_typing` — when -`In r (remove_first r R)`, the membership of every region is -preserved. ~15 LOC. Now Qed at `Semantics.v:3332`. - -**Why it doesn't close any admit**: the duplicate-r hypothesis -`In r (remove_first r R)` is never available in the touches_region -RIGHT branches. `S_Region_Exit`'s premise (`In r R`) only guarantees -single membership; the dual `In r (remove_first r R)` would require -multi-occurrence, which a well-typed initial program never -produces (`T_Region`'s `~In r R` premise prevents same-name -shadowing throughout reachable configurations). - -Lands as adjunct infrastructure: completes the case split alongside -`region_shrink_preserves_typing` and documents the explored -formulation. - -### III-b — `no_leaks` invariant as oracle (NOT VIABLE) - -`no_leaks` (line 374) proves only a memory-side fact: -`mem_read mu' l = Some (CString r s) -> False`. It says nothing -about whether `e'` is syntactically free of `r`. The bridge that -would make it useful — `step_exit_implies_free_of_exited_region` — -is **false** for congruence cases (the surviving sibling's -references to r break the freedom claim, as documented above at -"step_exit_implies_free_of_exited_region would be false"). -Re-engineering `no_leaks` to produce expression-side freedom would -require strengthening its conclusion from "memory free of r" to -"resulting expression syntactically free of r" — a different -judgement entirely, equivalent in effort to the auxiliary lemma -itself. III-b collapses to III-a / Option 2. - -### III-c — Restated preservation with membership-equivalence (WRONG AXIS) - -Weakening preservation's conclusion to `exists R'', (forall r, In r R'' -<-> In r R') /\ R''; G |- e' : T -| G'` does not help: 11 of the 12 -open goals are blocked on **G-context** Skolem alignment (already -solved by the Swarm-C `step_output_context_eq` splice), and the -12th wants genuine *weakening* (`r :: R'`), not membership- -equivalence. III-c diagnoses the wrong axis. - -### Conclusion - -The 12 admits do **not** dissolve under a clean lemma-level fix. -They require one of: - -1. **Mutual induction** with `preservation` and `step_preserves_type` - simultaneously (Option 1 above — the standard textbook approach - for region calculi, but a significant restructuring). -2. **Inversion-on-Hstep structural recursion** (~150 LOC, Option 2 - above) — orthogonal to the current case split; closes the single - `step_preserves_type_at_pre` admit at the source, which cascades - to the corresponding preservation admit. -3. **Type-system change**: add a sibling-region-disjointness side - condition to `T_Let`, `T_App`, etc. — ensures every well-typed - compound rules out the bad configuration where exit-able r is - shared with a sibling. Requires re-proving every existing lemma - under the new typing rule. - -Recommend Option 2 (~150 LOC, scope-isolated) over Option 1 -(refactor entire proof). Type-system change (Option 3) is the -"correct" fix for the underlying concrete-name encoding gap, but -the cost (re-prove every lemma) is high. - -## Unwind checklist (when finally closed) - -1. Replace `Admitted.` with `Qed.` -2. Flip `ROADMAP.adoc`'s admitted-proofs counter `1 → 0` -3. Flip `PROOF-NEEDS.md`'s status row + delete the "what needs - proving" item for `preservation` -4. Delete this file -5. Update `RUST-SPARK-STANCE.adoc`'s E1 row from OWED to DISCHARGED - (and remove the "honest gap" entry about preservation) -6. Delete the proof-status comment block at `Semantics.v` immediately - below the (now-`Qed.`) preservation diff --git a/formal/ROUTE-C-MIGRATION-PLAN.adoc b/formal/ROUTE-C-MIGRATION-PLAN.adoc new file mode 100644 index 00000000..ea7ca790 --- /dev/null +++ b/formal/ROUTE-C-MIGRATION-PLAN.adoc @@ -0,0 +1,211 @@ +== Route C — count-map carrier migration plan (to close ADMIT 1) + +*🛑 SUPERSEDED 2026-06-16 — DO NOT EXECUTE.* The Phase-1 gate proved +ADMIT 1 (`+region_shrink_preserves_typing_l1_gen_m+`) is *FALSE as +stated* (machine-checked counterexample: `+ERegion r (ERegion r EUnit)+` +consumes two ambient `+r+`s yet is `+expr_strictly_free_of_region r+`; +types at `+[r;r]->[]+` but not at `+[r]+`). No carrier change can prove +a false lemma, so route C is the wrong target. The honest fix is to +*restate region_shrink with the `+expr_no_exit_of_region r e+` guard* +(vacates the shadowed case) — closes ADMIT 1 on the existing list +carrier, no migration, and unifies with ADMIT 2. This document is +retained only as the record of the (now-moot) migration design + the A′ +decouple analysis. + +*Status: DRAFT for owner approval. No core edits made. 2026-06-16.* + +=== Why this, and why nothing cheaper + +ADMIT 1 = the single `+T_Region_Active_L1+` _shadowed_ sub-case +(`+rr = r+`) of `+region_shrink_preserves_typing_l1_gen_m+` +(`+Semantics_L1.v:576+`). Closing it on the *list* carrier is now proven +impossible by any reorder/permutation/insertion lemma: +`+T_Case_L1+`/`+T_If_L1+` (`+TypingL1.v:268-269+`/`+282-283+`) require +both branches to land on the _same_ output env, and +`+remove_first_L1+`’s first-occurrence semantics do not commute with +reordering (`+remove-first ∘ reorder ≠ reorder ∘ remove-first+`). The +count-map carrier removes "`first occurrence`" entirely — the env is a +function `+region_name → nat+` reasoned up to pointwise equality +`+renv_eq+`, so order is definitionally irrelevant. Stage 1 +(`+RegionEnvL1.v+`, committed `+b283c3b+`, axiom-free) already built the +carrier + the keystone +`+enter_exit_id : cntL1 r R >= 1 -> enterL1 r (exitL1 r R) =R= R+`. + +=== Scope / blast radius (honest) + +The L1 judgment `+has_type_l1+` is one inductive definition; changing +its region-env type is atomic — every downstream user breaks until +fixed. References to the L1 region env / `+TFunEff+`: + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|File |refs |role +|`+TypingL1.v+` |judgment + ~33 rules |the definition; `+_Eff+` decouple +lives here + +|`+Semantics_L1.v+` |~42 |all L1 metatheory; ADMIT 1 closes here; bulk +of the work + +|`+TypingL2.v+` |~28 |L2 modality layer built on `+has_type_l1+` + +|`+Counterexample_L2.v+` |~9 |L2 counterexamples; +`+eapply T_Lam_L1_Affine_Eff+` + +|`+Counterexample_L2_nested.v+` |~20 |ditto +|=== + +*Legacy stays untouched.* `+Typing.v+` / `+Semantics.v+` keep the list +carrier (`+region_env := list region_name+`, `+Syntax.v:867+`); +`+Counterexample.v+` depends on legacy falsity. The shared `+ty+` (and +therefore `+TFunEff+`, used by legacy) is *not* changed — which is +exactly why the `+_Eff+` footprint must be decoupled (below). This is a +multi-session migration that may uncover lemmas that do not translate; +the phased sequence is designed to surface that early. + +=== Carrier & ops (Stage 1, done) + +`+region_env_l1 := region_name -> nat+` (re-entry depth; 0 = dead), with +`+renv_eq R1 R2 := forall r, R1 r = R2 r+` (setoid, axiom-free). Ops: +`+cntL1+`, `+liveL1+` (`+R r >= 1+`), `+enterL1+` (increment), +`+exitL1+` (saturating decrement), `+emptyL1+`. Proper instances for all +ops already proved. + +=== The NEW keystone needed: `+Proper+`-on-judgment + +The migration’s load-bearing addition is a congruence instance: + +[source,coq] +---- +#[global] Instance has_type_l1_Proper : + Proper (eq ==> renv_eq ==> eq ==> eq ==> eq ==> renv_eq ==> eq ==> iff) + has_type_l1. +---- + +i.e. `+renv_eq+`-equal input/output envs are interchangeable in the +judgment. This is what lets the shadowed case rewrite +`+enterL1 r (exitL1 r R) =R= R+` (the keystone) _inside_ a typing goal. +It is proved by induction on the derivation, using the op-level Proper +instances — and it is *only sound because every rule uses +`+liveL1+`/`+enterL1+`/`+exitL1+` (which respect `+renv_eq+`) and never +inspects list structure.* Establishing this is the heart of Phase 2. + +=== Rule translation table (list → count-map) + +[cols=",",options="header",] +|=== +|List |Count-map +|`+In r R+` |`+liveL1 R r+` +|`+~ In r R+` |`+~ liveL1 R r+` (i.e. `+R r = 0+`) +|`+r :: R+` (push) |`+enterL1 r R+` +|`+remove_first_L1 r R+` (pop) |`+exitL1 r R+` +|`+R = R'+` (Leibniz) |`+R =R= R'+` (setoid) +|`+[]+` |`+emptyL1+` +|=== + +=== The A′ #18 decouple (the one real design decision) + +`+T_Lam_L1_Linear_Eff+` / `+_Affine_Eff+` currently reuse +`+R_in+`/`+R_out+` as *both* the body judgment env *and* the `+TFunEff+` +type annotation (`+list region_name+`, in the shared `+ty+`). Migration +splits the two roles: + +[source,coq] +---- +| T_Lam_L1_Linear_Eff : + forall R G T1 T2 e (Renv_in Renv_out : region_env_l1) + (Fin Fout : list region_name), + (forall r, liveL1 R r -> liveL1 Renv_in r) -> + Renv_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| Renv_out ; (T1, true) :: G -> + (forall r, In r Fin <-> liveL1 Renv_in r) -> (* bridge: footprint = live-set *) + (forall r, In r Fout <-> liveL1 Renv_out r) -> + R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 Fin Fout -| R ; G +---- + +* The judgment env is the count-map `+Renv_*+`; the `+TFunEff+` +annotation keeps its `+list+` footprints `+F*+` (so `+ty+` is unchanged +and legacy is untouched). +* The bridge is *relational* (`+In r F <-> liveL1 R r+`), _not_ a +`+support+` function — `+region_name -> nat+` is a total function with +no computable finite support, so it cannot be enumerated to a list +without a finiteness witness. +* *Cost at the L2 call sites:* the ~6 `+eapply T_Lam_L1_Affine_Eff+` in +`+Counterexample_L2*.v+` must now discharge the two bridge premises. At +those sites the envs are concrete (built from `+emptyL1+` by +`+enterL1+`), so each bridge is provable by `+unfold+`/decision — but it +is real per-site work. + +=== Phased sequence (fail-fast) + +*Phase 1 — proof-of-concept (CHEAP, the go/no-go gate, ~100 lines, +additive scratch).* Before touching the real judgment, build a _mini_ +judgment on the count-map carrier with only: the atomic rules, one +sequencing rule, `+T_Case+` (the join), and the four region rules. Prove +the `+has_type_l1_Proper+` analogue and the *shadowed region_shrink +case* via `+enter_exit_id+`. This definitively answers "`does the +count-map carrier actually close the shadowed case, and does the +`+Proper+` instance go through, and does `+T_Case+`’s join survive on +the new carrier?`" at 3% of the cost. *If Phase 1 does not close +cleanly, abort route C.* + +*Phase 2 — migrate `+TypingL1.v+`.* Translate the judgment env type + +all ~33 rules per the table; apply the `+_Eff+` decouple; add +`+has_type_l1_Proper+`. Green when `+TypingL1.v+` compiles. + +*Phase 3 — migrate `+Semantics_L1.v+` (the bulk).* Translate all ~42 +lemmas; *close ADMIT 1* at the shadowed case (active sub-case via +`+Proper+`+commutation; cnt=1 corner via `+enter_exit_id+` switching to +the fresh rule); keep admits 2/3/4 admitted (translated). Watch for +list-structural inductions on `+R+` (must become derivation inductions +or use a finiteness witness) — flag any lemma that enumerates `+R+`. +Green when `+Semantics_L1.v+` compiles with admit count 4→3. + +*Phase 4 — migrate `+TypingL2.v+` + `+Counterexample_L2*.v+`.* Thread +the new env type; discharge the `+_Eff+` bridge premises at each +counterexample call site. + +*Phase 5 — audit.* Full `+make+` from clean; `+Print Assumptions+` on +`+region_shrink_preserves_typing_l1_gen_m+` (and the L2 counterexamples) += "`Closed under the global context`"; confirm honest admit count 4→3 +and zero new axioms. + +=== How ADMIT 1 closes (the shadowed case) + +`+ERegion r e+`, shrinking `+r+`, `+e+` strictly-free-of-`+r+`. With the +count-map env, `+e+` preserves `+cnt r+` (no exit of `+r+`), so: - *cnt +r R ≥ 2:* `+r+` still live after the shrink → rebuild via +`+T_Region_Active_L1+`; the output equality is now a `+renv_eq+` fact +(`+exitL1+` commutation), discharged by `+Proper+`. - *cnt r R = 1:* +after the shrink `+r+` is fresh → rebuild via `+T_Region_L1+` (fresh); +`+enter_exit_id+` gives `+enterL1 r (exitL1 r R) =R= R+`, so the body +re-types and the output matches up to `+renv_eq+`. This is the exact +corner the list carrier could not express. + +=== Risk register / abort criteria + +[arabic] +. *Phase 1 fails* (shadowed case or `+Proper+` or `+T_Case+` join +doesn’t close on the mini-model) → *abort route C*, defer ADMIT 1. +_Cheap to discover._ +. *A Semantics_L1 lemma enumerates `+R+` as a list* (structural +induction on the env) → needs a finiteness-witness refactor or a +finite-map carrier instead of the total-function carrier; flag to owner, +may re-scope Stage 1. +. *An existing Qed lemma does not translate* → stop, surface; do not +admit-shuffle. +. *L2 bridge premises not dischargeable* at some counterexample site → +stop, surface (would indicate the set-bridge loses needed footprint +information). + +=== Owner sign-off needed before execution + +* *Approve the `+_Eff+` decouple shape* (relational set-bridge +`+In r F <-> liveL1 R r+`). +* *Approve big-bang vs branch-isolated* (recommend: dedicated branch off +`+proofs/l1-region-countmap-setoid+`, build red until Phase 4, never +merged until Phase 5 is green). +* *Net result:* honest admit count *4 → 3* (ADMIT 1 closed; 2/3/4 +remain; legacy `+preservation+` untouched). Effort: multi-session. + +*Recommended immediate next step:* execute *Phase 1 only* (additive +scratch, ~100 lines, reversible) as the go/no-go gate. Report, then +decide on Phases 2-5. diff --git a/formal/ROUTE-C-MIGRATION-PLAN.md b/formal/ROUTE-C-MIGRATION-PLAN.md deleted file mode 100644 index fd583c2f..00000000 --- a/formal/ROUTE-C-MIGRATION-PLAN.md +++ /dev/null @@ -1,177 +0,0 @@ - - - -# Route C — count-map carrier migration plan (to close ADMIT 1) - -**🛑 SUPERSEDED 2026-06-16 — DO NOT EXECUTE.** The Phase-1 gate proved ADMIT 1 -(`region_shrink_preserves_typing_l1_gen_m`) is **FALSE as stated** (machine-checked -counterexample: `ERegion r (ERegion r EUnit)` consumes two ambient `r`s yet is -`expr_strictly_free_of_region r`; types at `[r;r]->[]` but not at `[r]`). No carrier -change can prove a false lemma, so route C is the wrong target. The honest fix is to -**restate region_shrink with the `expr_no_exit_of_region r e` guard** (vacates the -shadowed case) — closes ADMIT 1 on the existing list carrier, no migration, and -unifies with ADMIT 2. This document is retained only as the record of the -(now-moot) migration design + the A′ decouple analysis. - -**Status: DRAFT for owner approval. No core edits made. 2026-06-16.** - -## Why this, and why nothing cheaper - -ADMIT 1 = the single `T_Region_Active_L1` *shadowed* sub-case (`rr = r`) of -`region_shrink_preserves_typing_l1_gen_m` (`Semantics_L1.v:576`). Closing it on -the **list** carrier is now proven impossible by any reorder/permutation/insertion -lemma: `T_Case_L1`/`T_If_L1` (`TypingL1.v:268-269`/`282-283`) require both branches -to land on the *same* output env, and `remove_first_L1`'s first-occurrence -semantics do not commute with reordering (`remove-first ∘ reorder ≠ reorder ∘ -remove-first`). The count-map carrier removes "first occurrence" entirely — the -env is a function `region_name → nat` reasoned up to pointwise equality -`renv_eq`, so order is definitionally irrelevant. Stage 1 (`RegionEnvL1.v`, -committed `b283c3b`, axiom-free) already built the carrier + the keystone -`enter_exit_id : cntL1 r R >= 1 -> enterL1 r (exitL1 r R) =R= R`. - -## Scope / blast radius (honest) - -The L1 judgment `has_type_l1` is one inductive definition; changing its region-env -type is atomic — every downstream user breaks until fixed. References to the L1 -region env / `TFunEff`: - -| File | refs | role | -|---|---|---| -| `TypingL1.v` | judgment + ~33 rules | the definition; `_Eff` decouple lives here | -| `Semantics_L1.v` | ~42 | all L1 metatheory; ADMIT 1 closes here; bulk of the work | -| `TypingL2.v` | ~28 | L2 modality layer built on `has_type_l1` | -| `Counterexample_L2.v` | ~9 | L2 counterexamples; `eapply T_Lam_L1_Affine_Eff` | -| `Counterexample_L2_nested.v` | ~20 | ditto | - -**Legacy stays untouched.** `Typing.v` / `Semantics.v` keep the list carrier -(`region_env := list region_name`, `Syntax.v:867`); `Counterexample.v` depends on -legacy falsity. The shared `ty` (and therefore `TFunEff`, used by legacy) is -**not** changed — which is exactly why the `_Eff` footprint must be decoupled -(below). This is a multi-session migration that may uncover lemmas that do not -translate; the phased sequence is designed to surface that early. - -## Carrier & ops (Stage 1, done) - -`region_env_l1 := region_name -> nat` (re-entry depth; 0 = dead), with -`renv_eq R1 R2 := forall r, R1 r = R2 r` (setoid, axiom-free). Ops: `cntL1`, -`liveL1` (`R r >= 1`), `enterL1` (increment), `exitL1` (saturating decrement), -`emptyL1`. Proper instances for all ops already proved. - -## The NEW keystone needed: `Proper`-on-judgment - -The migration's load-bearing addition is a congruence instance: - -```coq -#[global] Instance has_type_l1_Proper : - Proper (eq ==> renv_eq ==> eq ==> eq ==> eq ==> renv_eq ==> eq ==> iff) - has_type_l1. -``` - -i.e. `renv_eq`-equal input/output envs are interchangeable in the judgment. This -is what lets the shadowed case rewrite `enterL1 r (exitL1 r R) =R= R` (the -keystone) *inside* a typing goal. It is proved by induction on the derivation, -using the op-level Proper instances — and it is **only sound because every rule -uses `liveL1`/`enterL1`/`exitL1` (which respect `renv_eq`) and never inspects -list structure.** Establishing this is the heart of Phase 2. - -## Rule translation table (list → count-map) - -| List | Count-map | -|---|---| -| `In r R` | `liveL1 R r` | -| `~ In r R` | `~ liveL1 R r` (i.e. `R r = 0`) | -| `r :: R` (push) | `enterL1 r R` | -| `remove_first_L1 r R` (pop) | `exitL1 r R` | -| `R = R'` (Leibniz) | `R =R= R'` (setoid) | -| `[]` | `emptyL1` | - -## The A′ #18 decouple (the one real design decision) - -`T_Lam_L1_Linear_Eff` / `_Affine_Eff` currently reuse `R_in`/`R_out` as **both** -the body judgment env **and** the `TFunEff` type annotation (`list region_name`, -in the shared `ty`). Migration splits the two roles: - -```coq -| T_Lam_L1_Linear_Eff : - forall R G T1 T2 e (Renv_in Renv_out : region_env_l1) - (Fin Fout : list region_name), - (forall r, liveL1 R r -> liveL1 Renv_in r) -> - Renv_in ; ctx_extend G T1 |=L1[Linear] e : T2 -| Renv_out ; (T1, true) :: G -> - (forall r, In r Fin <-> liveL1 Renv_in r) -> (* bridge: footprint = live-set *) - (forall r, In r Fout <-> liveL1 Renv_out r) -> - R ; G |=L1[Linear] ELam T1 e : TFunEff T1 T2 Fin Fout -| R ; G -``` - -- The judgment env is the count-map `Renv_*`; the `TFunEff` annotation keeps its - `list` footprints `F*` (so `ty` is unchanged and legacy is untouched). -- The bridge is **relational** (`In r F <-> liveL1 R r`), *not* a `support` - function — `region_name -> nat` is a total function with no computable finite - support, so it cannot be enumerated to a list without a finiteness witness. -- **Cost at the L2 call sites:** the ~6 `eapply T_Lam_L1_Affine_Eff` in - `Counterexample_L2*.v` must now discharge the two bridge premises. At those - sites the envs are concrete (built from `emptyL1` by `enterL1`), so each bridge - is provable by `unfold`/decision — but it is real per-site work. - -## Phased sequence (fail-fast) - -**Phase 1 — proof-of-concept (CHEAP, the go/no-go gate, ~100 lines, additive -scratch).** Before touching the real judgment, build a *mini* judgment on the -count-map carrier with only: the atomic rules, one sequencing rule, `T_Case` -(the join), and the four region rules. Prove the `has_type_l1_Proper` analogue -and the **shadowed region_shrink case** via `enter_exit_id`. This definitively -answers "does the count-map carrier actually close the shadowed case, and does -the `Proper` instance go through, and does `T_Case`'s join survive on the new -carrier?" at 3% of the cost. **If Phase 1 does not close cleanly, abort route C.** - -**Phase 2 — migrate `TypingL1.v`.** Translate the judgment env type + all ~33 -rules per the table; apply the `_Eff` decouple; add `has_type_l1_Proper`. Green -when `TypingL1.v` compiles. - -**Phase 3 — migrate `Semantics_L1.v` (the bulk).** Translate all ~42 lemmas; -**close ADMIT 1** at the shadowed case (active sub-case via `Proper`+commutation; -cnt=1 corner via `enter_exit_id` switching to the fresh rule); keep admits 2/3/4 -admitted (translated). Watch for list-structural inductions on `R` (must become -derivation inductions or use a finiteness witness) — flag any lemma that -enumerates `R`. Green when `Semantics_L1.v` compiles with admit count 4→3. - -**Phase 4 — migrate `TypingL2.v` + `Counterexample_L2*.v`.** Thread the new env -type; discharge the `_Eff` bridge premises at each counterexample call site. - -**Phase 5 — audit.** Full `make` from clean; `Print Assumptions` on -`region_shrink_preserves_typing_l1_gen_m` (and the L2 counterexamples) = "Closed -under the global context"; confirm honest admit count 4→3 and zero new axioms. - -## How ADMIT 1 closes (the shadowed case) - -`ERegion r e`, shrinking `r`, `e` strictly-free-of-`r`. With the count-map env, -`e` preserves `cnt r` (no exit of `r`), so: -- **cnt r R ≥ 2:** `r` still live after the shrink → rebuild via - `T_Region_Active_L1`; the output equality is now a `renv_eq` fact - (`exitL1` commutation), discharged by `Proper`. -- **cnt r R = 1:** after the shrink `r` is fresh → rebuild via `T_Region_L1` - (fresh); `enter_exit_id` gives `enterL1 r (exitL1 r R) =R= R`, so the body - re-types and the output matches up to `renv_eq`. This is the exact corner the - list carrier could not express. - -## Risk register / abort criteria - -1. **Phase 1 fails** (shadowed case or `Proper` or `T_Case` join doesn't close on - the mini-model) → **abort route C**, defer ADMIT 1. *Cheap to discover.* -2. **A Semantics_L1 lemma enumerates `R` as a list** (structural induction on the - env) → needs a finiteness-witness refactor or a finite-map carrier instead of - the total-function carrier; flag to owner, may re-scope Stage 1. -3. **An existing Qed lemma does not translate** → stop, surface; do not admit-shuffle. -4. **L2 bridge premises not dischargeable** at some counterexample site → stop, - surface (would indicate the set-bridge loses needed footprint information). - -## Owner sign-off needed before execution - -- **Approve the `_Eff` decouple shape** (relational set-bridge `In r F <-> liveL1 R r`). -- **Approve big-bang vs branch-isolated** (recommend: dedicated branch off - `proofs/l1-region-countmap-setoid`, build red until Phase 4, never merged until - Phase 5 is green). -- **Net result:** honest admit count **4 → 3** (ADMIT 1 closed; 2/3/4 remain; - legacy `preservation` untouched). Effort: multi-session. - -**Recommended immediate next step:** execute **Phase 1 only** (additive scratch, -~100 lines, reversible) as the go/no-go gate. Report, then decide on Phases 2-5. diff --git a/formal/SUBST-LEMMA-GENERALIZATION-DESIGN.adoc b/formal/SUBST-LEMMA-GENERALIZATION-DESIGN.adoc new file mode 100644 index 00000000..340c6a6d --- /dev/null +++ b/formal/SUBST-LEMMA-GENERALIZATION-DESIGN.adoc @@ -0,0 +1,559 @@ +Copyright (c) Jonathan D.A. Jewell j.d.a.jewell@open.ac.uk –> + +== Design: generalising `+subst_typing_gen_l1_m+` to non-linear `+T1+` + +*Date*: 2026-05-28 *Author*: session 8 (Phase D slice 4 — +preservation_l2 follow-on) *Status*: design only; no code lands in this +doc’s PR + +=== TL;DR + +The β-case for `+T_App_L2_Eff+` in `+preservation_l2+` +(`+formal/TypingL2.v+`) requires substituting a value into a lambda body +when the lambda parameter type `+T1+` is *non-linear*. The existing +substitution lemma `+subst_typing_gen_l1_m+` +(`+formal/Semantics_L1.v:1358+`) carries an `+is_linear_ty T1 = true+` +precondition that blocks this. Generalising it is more complex than a +"`sibling lemma with `+false+` instead of `+true+``" — it interacts with +*body-R-rigidity* for non-linear `+ELam+` values. This document captures +the analysis, identifies the sub-problems, and recommends a phased +approach. + +=== Why this matters + +`+T_App_L2_Eff+` (`+formal/TypingL2.v:111+`, PR #209) is the L2 +elimination form for effect-typed lambdas. Closing `+preservation_l2+` +over `+has_type_l2+` requires handling its β-reduction case +(`+S_App_Fun+`). After inversion through `+L2_lift_l1+` + +`+T_Lam_L1_*_Eff+` and applying `+value_R_G_preserving_l1+` to the +argument value, the residual obligation is exactly: + +[source,coq] +---- +has_type_l1 m R G v T1 R G -> +has_type_l1 m R ((T1, false) :: G) ebody T2 R_out ((T1, true) :: G) -> +is_value v -> +has_type_l1 m R G (subst 0 v ebody) T2 R_out G +---- + +This is the signature of `+subst_typing_gen_l1_m+` at `+k = 0+`, EXCEPT +for the linearity precondition. For non-linear `+T1+` (any of `+TUnit+`, +`+TBool+`, `+TI32+`, `+TFun T1' T2'+`, `+TFunEff T1' T2' R_in' R_out'+`, +`+TProd …+` with non-linear components, `+TSum …+` with overall +non-linear, `+TRef Unr _+`, `+TEcho T'+`), the existing lemma cannot +fire. + +=== What `+subst_typing_gen_l1_m+` actually does with `+is_linear_ty T1 = true+` + +Read of `+Semantics_L1.v:1358-1656+`. Two distinct uses of `+Hlin+`: + +==== Use A: `+linear_value_is_loc_l1+` to extract canonical location form + +In ~20 of the proof’s 28 cases, the tactic pattern is: + +[source,coq] +---- +destruct (linear_value_is_loc_l1 _ _ _ _ Hv_type Hval Hlin) + as [lv [rv [-> [-> Hregv]]]]. +---- + +`+linear_value_is_loc_l1+` requires `+is_linear_ty T = true+` to +conclude `+v = ELoc l r ∧ T = TString r ∧ In r R+`. This canonical form +lets subsequent reasoning: - Treat `+v+` as a location (so substitution +into typing positions becomes "`introduce a location at the right +region`"). - Use `+loc_retype_at_R_l1_m+` to lift the location’s typing +across R-shifts within `+ebody+`’s recursion. + +For non-linear values, this canonical-form extraction has no analogue: - +`+EUnit+` / `+EBool+` / `+EI32+` have trivial R-irrelevance (T_Unit_L1 / +T_Bool_L1 / T_I32_L1 type them at any R). - `+ELam+` has body-R-rigidity +(the lambda’s body is typed at a fixed R determined at lambda +formation). - `+EPair+` / `+EInl+` / `+EInr+` are R-relevant if any +sub-component is R-relevant. - `+EBorrow+` of a location is R-relevant. + +==== Use B: contradiction discharge in `+T_Var_Unr_L1+` + +`+Semantics_L1.v:1409+`: + +[source,coq] +---- +exfalso. unfold ctx_lookup in H. rewrite Hk_in in H. +injection H as <- <-. rewrite Hlin in H0. discriminate. +---- + +When the substituted variable position `+k0+` is the same as the +variable being typed AND the variable is typed via T_Var_Unr_L1 +(unrestricted), the rule’s `+is_linear_ty T = false+` premise +contradicts `+Hlin : is_linear_ty T1 = true+` (since they refer to the +same type). The case is vacuous. + +For non-linear `+T1+`, this case is *not vacuous* — it’s the actual +substitution case: substituting an unrestricted value into an +unrestricted variable position. The proof structure must handle it +constructively rather than discharge by contradiction. + +=== The body-R-rigidity issue (the real obstacle) + +For the recursive cases in `+subst_typing_gen_l1_m+` (e.g. `+T_App_L1+`, +`+T_Pair_L1+`, `+T_Let_L1+`), the IH on each sub-expression needs +`+Hv_type+` at the *sub-expression’s* input R, which may differ from the +outer R due to region operations inside `+ebody+`. + +For linear values (locations), the existing proof lifts `+Hv_type+` +across R-shifts via: + +[source,coq] +---- +apply loc_retype_at_R_l1_m. eapply region_liveness_at_split_l1_gen; ... +---- + +or + +[source,coq] +---- +apply loc_retype_at_R_l1_m. right; exact Hregv. +---- + +The `+loc_retype_at_R_l1_m+` lemma re-types an `+ELoc+` at any `+R+` +that contains the location’s region. The corresponding non-linear-value +retype lemma is more nuanced: + +[width="100%",cols="50%,50%",options="header",] +|=== +|Value |Retype across R-shift `+R → R'+`? +|`+EUnit+` / `+EBool+` / `+EI32+` |✅ Trivial (R-irrelevant) + +|`+ELoc l r+` |❌ Always linear; outside non-linear scope + +|`+ELam T e+` (TFun T1 T2) |❌ Body typed at fixed R; retype requires +body-R-shift + +|`+ELam T e+` (TFunEff T1 T2 R_in R_out) |⚠️ Body typed at R_in +(independent of outer R); retype works if R’ still satisfies +`+forall r, In r R' -> In r R_in+` + +|`+EPair v1 v2+` |⚠️ Inherits from components + +|`+EInl T v+` / `+EInr T v+` |⚠️ Inherits from `+v+` + +|`+EBorrow (ELoc l r)+` |❌ Inherits R-dependence from `+ELoc+` + +|`+EEcho T v+` |⚠️ Inherits from `+v+` +|=== + +The ❌ rows mean a fully general `+nonlinear_value_retype_at_R_l1_m+` +lemma is *false* in the unrestricted case — specifically, `+TFun+` +lambdas have body-R-rigidity (the same gap that blocks legacy +`+preservation_l1+` per `+Semantics_L1.v:1708-1713+`). + +=== What this means for the lemma’s scope + +The clean non-linear substitution lemma cannot cover all non-linear +`+T1+`. The categories that *do* admit a clean lemma: + +[arabic] +. *Ground non-linear values* (`+EUnit+`, `+EBool+`, `+EI32+`): trivially +R-irrelevant. A retype lemma `+ground_nonlinear_retype_l1_m+` is +Qed-able in ~10 lines. +. *TFunEff lambdas* (`+ELam T e+` typed at +`+TFunEff T1 T2 R_in R_out+`): R’-retype works under the side condition +`+forall r, In r R' -> In r R_in+`. The lambda value’s typing rule +`+T_Lam_L1_*_Eff+` already requires this side condition at formation; +preserving it across retype is a one-line obligation. + +The categories that *do not* admit a clean lemma: + +[arabic, start=3] +. *TFun lambdas* (legacy `+T_Lam_L1_Linear+` / `+T_Lam_L1_Affine+`): +body-R-rigid. Same gap as legacy `+preservation_l1+` slice 4b. +. *EBorrow of a location*: inherits linear R-dependence; the borrow +doesn’t reduce R-dependence. +. *Compound values with R-dependent sub-components*: inherit from +sub-components. + +=== Recommended phasing + +==== Phase 1 (next session): ship `+ground_nonlinear_retype_l1_m+` + +A 10-line lemma covering `+EUnit+` / `+EBool+` / `+EI32+`. Qed-able +trivially. Useful in its own right (any β-reduction with +`+T1 ∈ {TUnit, TBool, TI32}+` becomes closeable). + +[source,coq] +---- +Lemma ground_nonlinear_retype_l1_m : + forall m R R' G v T, + is_value v -> + is_ground_nonlinear_ty T = true -> (* new predicate; TUnit/TBool/TI32 only *) + has_type_l1 m R G v T R G -> + has_type_l1 m R' G v T R' G. +---- + +Where `+is_ground_nonlinear_ty+` is a new predicate: + +[source,coq] +---- +Definition is_ground_nonlinear_ty (T : ty) : bool := + match T with + | TUnit | TBool | TI32 => true + | _ => false + end. +---- + +==== Phase 2: ship `+subst_typing_gen_l1_m_ground_nonlinear+` + +A parallel substitution lemma for ground non-linear types. Uses +`+ground_nonlinear_retype_l1_m+` in place of `+loc_retype_at_R_l1_m+` in +the cases that need R-shift retyping. + +Structure mirrors `+subst_typing_gen_l1_m+`’s 28 cases. Crucially: - +`+T_Var_Lin_L1+` with `+i = k0+`: *exfalso* via +`+is_linear_ty T1 = false+` vs T_Var_Lin_L1’s `+is_linear_ty T = true+` +premise. - `+T_Var_Unr_L1+` with `+i = k0+`: *constructive*; apply +`+Hv_type+` directly. - Compound cases: use +`+ground_nonlinear_retype_l1_m+` for R-shift retypes. + +Estimated ~250-300 lines, paralleling the existing proof. + +==== Phase 3: ship `+tfuneff_lambda_retype_l1_m+` + extend substitution + +Add a retype lemma for TFunEff lambdas (under R’ ⊆ R_in side condition). +Extend the substitution lemma to cover `+T1 = TFunEff …+` lambdas as +substituends. This is the case actually needed for higher-order +β-reductions where the lambda parameter is itself a function type. + +===== Phase 3b addendum (2026-05-30) — option (a) precondition is insufficient + +The originally-proposed option (a) precondition + +[source,coq] +---- +(forall r, In r (regions_introduced_by e) -> In r R_in_v) +---- + +is *insufficient* for the planned `+subst_typing_gen_l1_m_tfuneff+` +lemma. Filed as ephapax issue #235. + +*Why*: the substitution lemma recurses into inner lambda bodies +(mirroring Phase 2 lines 1929-1942). At inner `+T_Lam_L1_*_Eff+` cases, +the body sub-derivation is typed at the lambda’s declared +`+R_in_inner+`, which is *type-level* (lives in +`+TFunEff T1 T2 R_in_inner R_out_inner+`), not syntactic. +`+regions_introduced_by(e)+` only collects `+ERegion+` subterms’ +first-argument names; `+R_in_inner+` is invisible to it. + +`+tfuneff_lambda_retype_l1_m+` (the retype lemma shipped in PR #224) +requires `+R' ⊆ R_in_v+`. To retype the substituee at `+R_in_inner+` +we’d need `+R_in_inner ⊆ R_in_v+`. Nothing in option (a) provides this. + +Phase 2’s `+subst_typing_gen_l1_m_ground_nonlinear+` (the sibling lemma +at lines 1812-2073) dodges this via `+ground_nonlinear_retype_l1_m+`, +which is fully `+(m, R, G)+`-polymorphic. Phase 3b has no analogous +escape hatch. + +*Three resolution options for owner*: + +[arabic] +. *Strengthen the precondition to a type-level over-approximation*. +Define a helper that walks `+ELam T body+` extracting `+R_in+` from +`+T+` when `+T = TFunEff _ _ R_in _+`. *Blocker*: `+ELam+` syntax +carries the _parameter_ type, not the function type — `+R_in+` is +determined by typing, not syntax. Without an annotation extension to +`+ELam+`, this helper can’t be defined as a syntactic Fixpoint. +. *Semantic precondition over the derivation*. Quantify over +sub-derivations of `+Htype+`: +`+forall R'_in appearing in Htype, R'_in ⊆ R_in_v+`. Clean meaning, +awkward in Coq (inductive predicate over derivations or fixpoint indexed +by depth). +. *Restrict scope — Phase 3b leaf-only*. Add an inductive predicate +`+lambda_free e+` (or `+tfuneff_lambda_free e+`) and condition the lemma +on it. Unblocks the *immediate* consumer (preservation_l2 β-case for +TFunEff arguments whose ambient `+ebody+` uses ERegion but not nested +function abstractions). Recursive case rides Phase 5’s compound-value +redesign. + +*Recommendation*: option (3) as the tactical landing for Phase 3b. The +substantive Phase 5 redesign can subsume option (1) or (2) at its +leisure. + +Phase 3b implementation *does not start* until owner picks a resolution. + +===== Phase 3b resolution (2026-05-30 PM, owner-approved) — 4-stage staged plan + +The three-option framing above is *superseded* by a staged plan that +captures the value of each "`Interesting`" angle without committing to +any single option’s downsides. Filed as ephapax issues #239 (Stage 1) / +#240 (Stage 2) / #241 (Stage 3) / #242 (Stage 4) under parent #235. + +[width="100%",cols="34%,33%,33%",options="header",] +|=== +|Stage |Scope |Captures value of +|*Stage 1 — immediate (#239)* |Leaf-only Phase 3b via +`+tfuneff_lambda_free+` + `+Counterexample_L2_nested.v+` + 2-condition +`+preservation_l2+`. |Option (3) — principled deferral, honest +2-condition statement. + +|*Stage 2 — parallel L4 track (#240)* |`+ELam T_param R_in R_out body+` +annotation extension. AST + typing rule + cascading inversion patterns. +|Option (1) — L4 alignment "`type-level → program-level commitments`". + +|*Stage 3 — post-Stage-2 (#241)* |Relaxed Phase 3b via +`+declared_lambda_r_ins ⊆ R_in_v+` + *CPS-form* v-typing argument. +Nested-condition collapses. |Option (2) — higher-order proof style +enters the codebase. + +|*Stage 4 — Phase 5 (#242)* |Compound non-linear values + +region-substitution machinery + *unconditional* `+preservation_l2+`. +|The final destination — last soundness condition closes. +|=== + +*Why staging captures all the value*: + +* *Stage 1’s 2-condition statement is delivered correctness, not a +placeholder.* Each condition has a mechanised counterexample +(`+Counterexample_L2.v+` for the fresh-region gap, the new +`+Counterexample_L2_nested.v+` for the nested-lambda gap). +* *Stage 2 ships independently* of Phase 3b — it’s L4’s own work +(program-level commitments) that Phase 3b free-rides on at Stage 3. +* *Stage 3 introduces CPS proof style* at exactly the point where it’s +strictly necessary (relaxed Phase 3b’s inner `+T_Lam_L1_*_Eff+` cases +must retype v at arbitrary `+R_in_inner+`’s) — not over-engineered for +Stage 1. +* *Stage 4 inherits the CPS precedent* from Stage 3 and closes the last +soundness condition. `+preservation_l2+` Qed over `+has_type_l2+`. + +*Why this beats single-option commitments*: + +* {blank} +[arabic] +. alone forces the AST migration before unblocking preservation_l2’s +β-case. +* {blank} +[arabic, start=2] +. alone introduces an inductive-over-derivations predicate with no other +use in the codebase. +* {blank} +[arabic, start=3] +. alone ends with a conditional preservation_l2 forever (no path to +unconditional). + +The staged plan: (3) ships today’s value, (1) lands L4’s value at L4’s +timeline, (2)’s value is harvested at exactly the point CPS is +necessary, (4) reaches unconditional preservation_l2 as the natural sum +of (1) + (2) + (3) applied in sequence. + +*Sequencing*: Stage 1 implementation green-lit. Stages 2-4 tracked. +Stage 3 blocked on Stage 2; Stage 4 blocked on Stage 3; Stage 2 +independent of Stage 1. + +===== Phase 3b Stage 1a (2026-05-30 PM, landed) — split from Stage 1 + +Stage 1’s deliverables split into two slices for shipping cadence: + +*Stage 1a (this PR, landed)* — Infrastructure + soundness-gap witness: - +`+tfuneff_lambda_free : expr -> bool+` Fixpoint in `+formal/Syntax.v+`. +Conservative leaf-only predicate: `+false+` on every `+ELam+`, `+true+` +elsewhere; propagates compositionally through compound forms via +`+andb+`. - `+formal/Counterexample_L2_nested.v+` — five `+Qed+` lemmas +(`+v_typed_at_empty+`, `+outer_typed+`, `+e_before_typed+`, `+e_step+`, +`+e_after_untypable+`) mechanising the nested-TFunEff soundness gap. +Configuration: `+outer = ELam T_v (ELam (TBase TUnit) (EVar 1))+` with +inner `+R_in_inner = [r2]+`; `+v = ELam TUnit EUnit+` at +`+TFunEff TUnit TUnit [] []+`. Post-β `+e_after = ELam TUnit v+` cannot +retype the body at `+[r2] ⊄ [] = R_in_v+`. Sibling artifact to +`+Counterexample_L2.v+`: together the two files justify the +*two-condition* preservation_l2 statement Stage 1 ships (P1 = +`+tfuneff_lambda_free ebody+`, P2 = +`+regions_introduced_by ebody ⊆ R_in_v+`). - Wired into `+_CoqProject+` +after `+Counterexample_L2.v+`. - Zero new admits / axioms (Print +Assumptions: all five lemmas Closed under the global context). + +*Stage 1b (follow-up issue, deferred)* — Substitution lemma + +preservation wrapper: - `+subst_typing_gen_l1_m_tfuneff+` Qed in +`+formal/Semantics_L1.v+` mirroring +`+subst_typing_gen_l1_m_ground_nonlinear+` (~300 lines); inner +`+T_Lam_L1_*_Eff+` cases exfalso via `+tfuneff_lambda_free+`; direct +(P1, P2) hypothesis form. - `+preservation_l2_app_eff_beta_tfuneff_l1+` ++ L2 wrapper Qed in `+formal/TypingL2.v+`. - The structural blocker +requiring deferral: the substitution lemma’s compound rule cases +(T_Let_L1, T_LetLin_L1, T_Case_L1__) need to retype the substituent +value `+v+` at a different G (post-e1 used-flag updates). Phase 2’s +analog uses `+ground_nonlinear_retype_l1_m+` (R-poly AND G-poly); Phase +3b’s `+tfuneff_lambda_retype_l1_m+` preserves G. The Stage 1b machinery +needs EITHER (a) a closed-value G-polymorphism helper +(`+closed_value_typing_G_poly_l1_m+` — provable via inversion on +T_Lam_L1___Eff formation rules but lengthy), OR (b) a G-flag-polymorphic +retype variant. Either path is its own sub-deliverable. + +==== Phase 4: close `+preservation_l2+` β-case using Phases 1-3 + +With the substitution machinery in place, the T_App_L2_Eff β-case in +`+preservation_l2+` closes by: 1. Inversion on the L2 derivation → L1 +derivation of `+T_Lam_L1_*_Eff+`. 2. Inversion on the L1 derivation → +body typing at R_in + side condition. 3. Apply +`+value_R_G_preserving_l1+` to argument value → R_in = R, G’’ = G. 4. +Case-split on `+is_linear_ty T1+`: - Linear: use existing +`+subst_typing_gen_l1_m+`. - Ground non-linear: use Phase 2 lemma. - +TFunEff non-linear: use Phase 3 lemma. - Other non-linear: deferred +(compound values; see Phase 5). 5. `+L2_lift_l1+` wrap. + +==== Phase 5 (deferred): compound non-linear values + +`+EPair+` / `+EInl+` / `+EInr+` / `+EEcho+` of non-linear components. +Sub-component analysis. May require additional retype machinery. +Realistically multiple sessions of work. + +=== Phase 4c addendum (2026-05-30) — conditional preservation_l2 for TFunEff β + +Prototyping Phase 4c on paper (after Phase 4a / 4b landed at PRs #228 / +#233) revealed a structural soundness gap that requires a *conditional* +preservation_l2 statement for the TFunEff β-case. +`+formal/Counterexample_L2.v+` mechanises the witness. + +==== The obstacle (mechanised in `+Counterexample_L2.v+`) + +For any TFunEff lambda body that introduces a fresh region via +`+ERegion+` AND references the substituted variable inside that region +scope, β-reduction does *not* preserve typing: + +.... +T1_inner = TFunEff TUnit TUnit [] [] (* substituee type, R_in_v = [] *) +outer = ELam T1_inner (ERegion r2 (EVar 0)) (* body introduces fresh r2 *) +v2 = ELam TUnit EUnit (* a value of type T1_inner *) +e_before = EApp outer v2 (* well-typed via T_App_L2_Eff at R = [] *) +e_after = ERegion r2 v2 (* β-result: subst 0 v2 (ERegion r2 (EVar 0)) *) +.... + +`+e_before+` types (Qed: `+e_before_typed+`); `+e_step+` reduces it to +`+e_after+` (Qed: `+e_step+`); `+e_after+` does not type at the same +outer type (Qed: `+e_after_untypable+`). The mechanism: T_Region_L1’s +`+~ In r (free_regions T)+` premise prevents the fresh `+r+` from being +in `+R_in_v+` (since `+R_in_v ⊆ free_regions(TFunEff)+`). After +β-substitution, the inner expression becomes a TFunEff value that must +re-type at `+r :: R+`, requiring `+r ∈ R_in_v+` (false). + +==== Resolution: Phase 4c ships *conditionally* + +Preservation_l2 for the T_App_L2_Eff β-case holds *only* for programs +satisfying: + +.... +regions_introduced_by(ebody) ⊆ R_in_v +.... + +where `+regions_introduced_by+` is the `+Fixpoint+` already landed in +`+Syntax.v+` (PR #230), `+ebody+` is the outer lambda’s body, and +`+R_in_v+` is the substituee’s TFunEff input region env. + +The Phase 3b substitution lemma (`+subst_typing_gen_l1_m_tfuneff+`) +ships with this precondition; the Phase 4c β-case wrapper propagates it. +Programs not satisfying the precondition are a *documented soundness-gap +subclass* — they witness the same fundamental "`scoped resource cannot +escape its scope`" limitation that motivated the four-layer redesign in +the first place. + +==== Three resolution paths (broader than original (a)/(b)/(c) options) + +[arabic] +. *Conditional preservation_l2 (recommended)*: Phase 3b lemma takes the +precondition; Phase 4c β-case requires it; programs outside form a +documented soundness-gap class. This is what `+Counterexample_L2.v+` +justifies. Aligns with the legacy `+Counterexample.v+` precedent +(preservation holds modulo a structural constraint that legitimate +programs satisfy). +. *Region-polymorphic TFunEff*: change the type system so +`+TFunEff T1 T2 R_in R_out+` permits implicit region extension at use +sites. Major type-system change; defers to a future redesign. +. *L2 region-transfer combinator*: add an L2 typing rule that explicitly +transfers a fresh region into a TFunEff lambda’s R_in for the duration +of a scope. Adds L2 expressiveness; defers to a future PR. + +==== What ships in the Phase 4c PR + +* `+formal/Counterexample_L2.v+` — five Qed lemmas mechanising the +soundness gap. +* This addendum to `+SUBST-LEMMA-GENERALIZATION-DESIGN.md+`. +* STATE.a2ml refresh to reflect the conditional path. + +Phase 3b implementation (the ~400-line `+subst_typing_gen_l1_m_tfuneff+` +lemma body) and Phase 4c β-case wrapper remain follow-up work — but the +design constraint they must satisfy is now mechanically witnessed. + +=== What this session ships + +This design document only. No code changes. STATE.a2ml shifts +`+next_action+` to "`Phase 1: implement `+ground_nonlinear_retype_l1_m+` +per `+formal/SUBST-LEMMA-GENERALIZATION-DESIGN.md+``". + +=== Owner-directive compliance check + +Per `+CLAUDE.md+` 2026-05-27: + +* ✅ Does not propose closing legacy `+preservation+` in `+Semantics.v+` +(provably false). +* ✅ Does not extend `+Semantics.v+` with closure-support lemmas. +* ✅ Does not close residual `+Semantics_L1.v+` axioms via proof tricks +— Phase 1-2 add NEW infrastructure to `+Semantics_L1.v+` orthogonal to +legacy admits. +* ✅ Does not follow pre-2026-05-26 closure plans. +* ✅ Does not patch legacy `+Typing.v+`. +* ✅ Reads `+PRESERVATION-DESIGN.md+` first (specifically §5.1 lines +468-474 — the L1-intro / L2-elim design vision endorsing T_App_L2_Eff at +L2). +* ✅ Works per-layer (L1 infrastructure → L2 preservation closure). +* ✅ Escalates before patching (this design doc IS the escalation). + +Anti-pattern detector (per `+CLAUDE.md+` §"`Anti-pattern detector`"): + +* ✅ No sibling-region-disjointness side conditions proposed. +* ✅ No region-weakening predicates indexed on syntactic shape (the +proposed retype lemmas are indexed on TYPE shape, not syntactic shape — +`+is_ground_nonlinear_ty+` is a type predicate). +* ✅ No admit-shuffling between `+Semantics.v+` and a new lemma. +* ✅ No proposal to close `+Theorem preservation+` in `+Semantics.v+` to +`+Qed.+`. +* ✅ No new `+Axiom+` declarations. + +=== Open design questions for owner + +[arabic] +. *`+is_ground_nonlinear_ty+` predicate placement*: `+Syntax.v+` (near +`+is_linear_ty+`) or `+Semantics_L1.v+` (near the lemma that uses it)? +Recommendation: `+Syntax.v+` for symmetry with `+is_linear_ty+`. +. *Phase 3 scope*: TFunEff lambdas only, or also TFun lambdas with the +body-R-rigidity gap honestly admitted? Recommendation: TFunEff only +(TFun is the legacy slice 4b debt; not in scope for this initiative). +. *Phase 5 priority*: should compound non-linear values block +`+preservation_l2+` closure, or can `+preservation_l2+` Qed with the +compound-value case admitted? Recommendation: defer Phase 5; +`+preservation_l2+` can close conditionally on a +`+nonlinear_compound_substitutable T+` predicate that returns true for +ground + TFunEff and false otherwise. The predicate’s `+true+` cases let +preservation_l2 fire; the `+false+` cases vacuously discharge or admit +at the predicate-false branch. +. *Sibling vs case-split*: Phase 2 ships as a parallel lemma +(`+subst_typing_gen_l1_m_ground_nonlinear+`) rather than folding +case-split into the existing lemma. Avoids breaking ~30 Qed downstreams +of `+subst_typing_gen_l1_m+`. Confirmed. + +=== References + +* `+formal/Semantics_L1.v:1358-1656+` — `+subst_typing_gen_l1_m+` proof +body. +* `+formal/Semantics_L1.v:916-933+` — `+linear_value_is_loc_l1+` +canonical-form extractor. +* `+formal/Semantics_L1.v:1168-1235+` — `+loc_retype_at_R_l1_m+` (the +lemma to parallel for non-linear values). +* `+formal/Semantics_L1.v:1708-1713+` — body-R-rigidity comment in +preservation_l1. +* `+formal/TypingL1.v:221-229+` — `+T_Lam_L1_Linear_Eff+` / +`+T_Lam_L1_Affine_Eff+` (TFunEff lambdas with R_in side condition). +* `+formal/TypingL2.v+` post-PR #211 — `+preservation_l2_via_l1+` + doc +block stating the full preservation_l2 goal. +* `+formal/PRESERVATION-DESIGN.md+` §5.1 lines 468-474 — load-bearing +design quote. +* `+CLAUDE.md+` owner directive 2026-05-27 — preservation-work +boundaries. +* PR #209 — T_App_L2_Eff constructor. +* PR #211 — preservation_l2_via_l1. +* PR #212 — STATE shift after PR #211. diff --git a/formal/SUBST-LEMMA-GENERALIZATION-DESIGN.md b/formal/SUBST-LEMMA-GENERALIZATION-DESIGN.md deleted file mode 100644 index ba900479..00000000 --- a/formal/SUBST-LEMMA-GENERALIZATION-DESIGN.md +++ /dev/null @@ -1,328 +0,0 @@ - -Copyright (c) Jonathan D.A. Jewell ---> - - -# Design: generalising `subst_typing_gen_l1_m` to non-linear `T1` - -**Date**: 2026-05-28 -**Author**: session 8 (Phase D slice 4 — preservation_l2 follow-on) -**Status**: design only; no code lands in this doc's PR - -## TL;DR - -The β-case for `T_App_L2_Eff` in `preservation_l2` (`formal/TypingL2.v`) requires substituting a value into a lambda body when the lambda parameter type `T1` is **non-linear**. The existing substitution lemma `subst_typing_gen_l1_m` (`formal/Semantics_L1.v:1358`) carries an `is_linear_ty T1 = true` precondition that blocks this. Generalising it is more complex than a "sibling lemma with `false` instead of `true`" — it interacts with **body-R-rigidity** for non-linear `ELam` values. This document captures the analysis, identifies the sub-problems, and recommends a phased approach. - -## Why this matters - -`T_App_L2_Eff` (`formal/TypingL2.v:111`, PR #209) is the L2 elimination form for effect-typed lambdas. Closing `preservation_l2` over `has_type_l2` requires handling its β-reduction case (`S_App_Fun`). After inversion through `L2_lift_l1` + `T_Lam_L1_*_Eff` and applying `value_R_G_preserving_l1` to the argument value, the residual obligation is exactly: - -```coq -has_type_l1 m R G v T1 R G -> -has_type_l1 m R ((T1, false) :: G) ebody T2 R_out ((T1, true) :: G) -> -is_value v -> -has_type_l1 m R G (subst 0 v ebody) T2 R_out G -``` - -This is the signature of `subst_typing_gen_l1_m` at `k = 0`, EXCEPT for the linearity precondition. For non-linear `T1` (any of `TUnit`, `TBool`, `TI32`, `TFun T1' T2'`, `TFunEff T1' T2' R_in' R_out'`, `TProd …` with non-linear components, `TSum …` with overall non-linear, `TRef Unr _`, `TEcho T'`), the existing lemma cannot fire. - -## What `subst_typing_gen_l1_m` actually does with `is_linear_ty T1 = true` - -Read of `Semantics_L1.v:1358-1656`. Two distinct uses of `Hlin`: - -### Use A: `linear_value_is_loc_l1` to extract canonical location form - -In ~20 of the proof's 28 cases, the tactic pattern is: - -```coq -destruct (linear_value_is_loc_l1 _ _ _ _ Hv_type Hval Hlin) - as [lv [rv [-> [-> Hregv]]]]. -``` - -`linear_value_is_loc_l1` requires `is_linear_ty T = true` to conclude `v = ELoc l r ∧ T = TString r ∧ In r R`. This canonical form lets subsequent reasoning: -- Treat `v` as a location (so substitution into typing positions becomes "introduce a location at the right region"). -- Use `loc_retype_at_R_l1_m` to lift the location's typing across R-shifts within `ebody`'s recursion. - -For non-linear values, this canonical-form extraction has no analogue: -- `EUnit` / `EBool` / `EI32` have trivial R-irrelevance (T_Unit_L1 / T_Bool_L1 / T_I32_L1 type them at any R). -- `ELam` has body-R-rigidity (the lambda's body is typed at a fixed R determined at lambda formation). -- `EPair` / `EInl` / `EInr` are R-relevant if any sub-component is R-relevant. -- `EBorrow` of a location is R-relevant. - -### Use B: contradiction discharge in `T_Var_Unr_L1` - -`Semantics_L1.v:1409`: -```coq -exfalso. unfold ctx_lookup in H. rewrite Hk_in in H. -injection H as <- <-. rewrite Hlin in H0. discriminate. -``` - -When the substituted variable position `k0` is the same as the variable being typed AND the variable is typed via T_Var_Unr_L1 (unrestricted), the rule's `is_linear_ty T = false` premise contradicts `Hlin : is_linear_ty T1 = true` (since they refer to the same type). The case is vacuous. - -For non-linear `T1`, this case is **not vacuous** — it's the actual substitution case: substituting an unrestricted value into an unrestricted variable position. The proof structure must handle it constructively rather than discharge by contradiction. - -## The body-R-rigidity issue (the real obstacle) - -For the recursive cases in `subst_typing_gen_l1_m` (e.g. `T_App_L1`, `T_Pair_L1`, `T_Let_L1`), the IH on each sub-expression needs `Hv_type` at the **sub-expression's** input R, which may differ from the outer R due to region operations inside `ebody`. - -For linear values (locations), the existing proof lifts `Hv_type` across R-shifts via: -```coq -apply loc_retype_at_R_l1_m. eapply region_liveness_at_split_l1_gen; ... -``` -or -```coq -apply loc_retype_at_R_l1_m. right; exact Hregv. -``` - -The `loc_retype_at_R_l1_m` lemma re-types an `ELoc` at any `R` that contains the location's region. The corresponding non-linear-value retype lemma is more nuanced: - -| Value | Retype across R-shift `R → R'`? | -|---|---| -| `EUnit` / `EBool` / `EI32` | ✅ Trivial (R-irrelevant) | -| `ELoc l r` | ❌ Always linear; outside non-linear scope | -| `ELam T e` (TFun T1 T2) | ❌ Body typed at fixed R; retype requires body-R-shift | -| `ELam T e` (TFunEff T1 T2 R_in R_out) | ⚠️ Body typed at R_in (independent of outer R); retype works if R' still satisfies `forall r, In r R' -> In r R_in` | -| `EPair v1 v2` | ⚠️ Inherits from components | -| `EInl T v` / `EInr T v` | ⚠️ Inherits from `v` | -| `EBorrow (ELoc l r)` | ❌ Inherits R-dependence from `ELoc` | -| `EEcho T v` | ⚠️ Inherits from `v` | - -The ❌ rows mean a fully general `nonlinear_value_retype_at_R_l1_m` lemma is **false** in the unrestricted case — specifically, `TFun` lambdas have body-R-rigidity (the same gap that blocks legacy `preservation_l1` per `Semantics_L1.v:1708-1713`). - -## What this means for the lemma's scope - -The clean non-linear substitution lemma cannot cover all non-linear `T1`. The categories that **do** admit a clean lemma: - -1. **Ground non-linear values** (`EUnit`, `EBool`, `EI32`): trivially R-irrelevant. A retype lemma `ground_nonlinear_retype_l1_m` is Qed-able in ~10 lines. - -2. **TFunEff lambdas** (`ELam T e` typed at `TFunEff T1 T2 R_in R_out`): R'-retype works under the side condition `forall r, In r R' -> In r R_in`. The lambda value's typing rule `T_Lam_L1_*_Eff` already requires this side condition at formation; preserving it across retype is a one-line obligation. - -The categories that **do not** admit a clean lemma: - -3. **TFun lambdas** (legacy `T_Lam_L1_Linear` / `T_Lam_L1_Affine`): body-R-rigid. Same gap as legacy `preservation_l1` slice 4b. - -4. **EBorrow of a location**: inherits linear R-dependence; the borrow doesn't reduce R-dependence. - -5. **Compound values with R-dependent sub-components**: inherit from sub-components. - -## Recommended phasing - -### Phase 1 (next session): ship `ground_nonlinear_retype_l1_m` - -A 10-line lemma covering `EUnit` / `EBool` / `EI32`. Qed-able trivially. Useful in its own right (any β-reduction with `T1 ∈ {TUnit, TBool, TI32}` becomes closeable). - -```coq -Lemma ground_nonlinear_retype_l1_m : - forall m R R' G v T, - is_value v -> - is_ground_nonlinear_ty T = true -> (* new predicate; TUnit/TBool/TI32 only *) - has_type_l1 m R G v T R G -> - has_type_l1 m R' G v T R' G. -``` - -Where `is_ground_nonlinear_ty` is a new predicate: -```coq -Definition is_ground_nonlinear_ty (T : ty) : bool := - match T with - | TUnit | TBool | TI32 => true - | _ => false - end. -``` - -### Phase 2: ship `subst_typing_gen_l1_m_ground_nonlinear` - -A parallel substitution lemma for ground non-linear types. Uses `ground_nonlinear_retype_l1_m` in place of `loc_retype_at_R_l1_m` in the cases that need R-shift retyping. - -Structure mirrors `subst_typing_gen_l1_m`'s 28 cases. Crucially: -- `T_Var_Lin_L1` with `i = k0`: **exfalso** via `is_linear_ty T1 = false` vs T_Var_Lin_L1's `is_linear_ty T = true` premise. -- `T_Var_Unr_L1` with `i = k0`: **constructive**; apply `Hv_type` directly. -- Compound cases: use `ground_nonlinear_retype_l1_m` for R-shift retypes. - -Estimated ~250-300 lines, paralleling the existing proof. - -### Phase 3: ship `tfuneff_lambda_retype_l1_m` + extend substitution - -Add a retype lemma for TFunEff lambdas (under R' ⊆ R_in side condition). Extend the substitution lemma to cover `T1 = TFunEff …` lambdas as substituends. This is the case actually needed for higher-order β-reductions where the lambda parameter is itself a function type. - -#### Phase 3b addendum (2026-05-30) — option (a) precondition is insufficient - -The originally-proposed option (a) precondition - -```coq -(forall r, In r (regions_introduced_by e) -> In r R_in_v) -``` - -is **insufficient** for the planned `subst_typing_gen_l1_m_tfuneff` lemma. Filed as ephapax issue #235. - -**Why**: the substitution lemma recurses into inner lambda bodies (mirroring Phase 2 lines 1929-1942). At inner `T_Lam_L1_*_Eff` cases, the body sub-derivation is typed at the lambda's declared `R_in_inner`, which is **type-level** (lives in `TFunEff T1 T2 R_in_inner R_out_inner`), not syntactic. `regions_introduced_by(e)` only collects `ERegion` subterms' first-argument names; `R_in_inner` is invisible to it. - -`tfuneff_lambda_retype_l1_m` (the retype lemma shipped in PR #224) requires `R' ⊆ R_in_v`. To retype the substituee at `R_in_inner` we'd need `R_in_inner ⊆ R_in_v`. Nothing in option (a) provides this. - -Phase 2's `subst_typing_gen_l1_m_ground_nonlinear` (the sibling lemma at lines 1812-2073) dodges this via `ground_nonlinear_retype_l1_m`, which is fully `(m, R, G)`-polymorphic. Phase 3b has no analogous escape hatch. - -**Three resolution options for owner**: - -1. **Strengthen the precondition to a type-level over-approximation**. Define a helper that walks `ELam T body` extracting `R_in` from `T` when `T = TFunEff _ _ R_in _`. **Blocker**: `ELam` syntax carries the *parameter* type, not the function type — `R_in` is determined by typing, not syntax. Without an annotation extension to `ELam`, this helper can't be defined as a syntactic Fixpoint. - -2. **Semantic precondition over the derivation**. Quantify over sub-derivations of `Htype`: `forall R'_in appearing in Htype, R'_in ⊆ R_in_v`. Clean meaning, awkward in Coq (inductive predicate over derivations or fixpoint indexed by depth). - -3. **Restrict scope — Phase 3b leaf-only**. Add an inductive predicate `lambda_free e` (or `tfuneff_lambda_free e`) and condition the lemma on it. Unblocks the **immediate** consumer (preservation_l2 β-case for TFunEff arguments whose ambient `ebody` uses ERegion but not nested function abstractions). Recursive case rides Phase 5's compound-value redesign. - -**Recommendation**: option (3) as the tactical landing for Phase 3b. The substantive Phase 5 redesign can subsume option (1) or (2) at its leisure. - -Phase 3b implementation **does not start** until owner picks a resolution. - -#### Phase 3b resolution (2026-05-30 PM, owner-approved) — 4-stage staged plan - -The three-option framing above is **superseded** by a staged plan that captures the value of each "Interesting" angle without committing to any single option's downsides. Filed as ephapax issues #239 (Stage 1) / #240 (Stage 2) / #241 (Stage 3) / #242 (Stage 4) under parent #235. - -| Stage | Scope | Captures value of | -|---|---|---| -| **Stage 1 — immediate (#239)** | Leaf-only Phase 3b via `tfuneff_lambda_free` + `Counterexample_L2_nested.v` + 2-condition `preservation_l2`. | Option (3) — principled deferral, honest 2-condition statement. | -| **Stage 2 — parallel L4 track (#240)** | `ELam T_param R_in R_out body` annotation extension. AST + typing rule + cascading inversion patterns. | Option (1) — L4 alignment "type-level → program-level commitments". | -| **Stage 3 — post-Stage-2 (#241)** | Relaxed Phase 3b via `declared_lambda_r_ins ⊆ R_in_v` + **CPS-form** v-typing argument. Nested-condition collapses. | Option (2) — higher-order proof style enters the codebase. | -| **Stage 4 — Phase 5 (#242)** | Compound non-linear values + region-substitution machinery + **unconditional** `preservation_l2`. | The final destination — last soundness condition closes. | - -**Why staging captures all the value**: - -- **Stage 1's 2-condition statement is delivered correctness, not a placeholder.** Each condition has a mechanised counterexample (`Counterexample_L2.v` for the fresh-region gap, the new `Counterexample_L2_nested.v` for the nested-lambda gap). -- **Stage 2 ships independently** of Phase 3b — it's L4's own work (program-level commitments) that Phase 3b free-rides on at Stage 3. -- **Stage 3 introduces CPS proof style** at exactly the point where it's strictly necessary (relaxed Phase 3b's inner `T_Lam_L1_*_Eff` cases must retype v at arbitrary `R_in_inner`'s) — not over-engineered for Stage 1. -- **Stage 4 inherits the CPS precedent** from Stage 3 and closes the last soundness condition. `preservation_l2` Qed over `has_type_l2`. - -**Why this beats single-option commitments**: - -- (1) alone forces the AST migration before unblocking preservation_l2's β-case. -- (2) alone introduces an inductive-over-derivations predicate with no other use in the codebase. -- (3) alone ends with a conditional preservation_l2 forever (no path to unconditional). - -The staged plan: (3) ships today's value, (1) lands L4's value at L4's timeline, (2)'s value is harvested at exactly the point CPS is necessary, (4) reaches unconditional preservation_l2 as the natural sum of (1) + (2) + (3) applied in sequence. - -**Sequencing**: Stage 1 implementation green-lit. Stages 2-4 tracked. Stage 3 blocked on Stage 2; Stage 4 blocked on Stage 3; Stage 2 independent of Stage 1. - -#### Phase 3b Stage 1a (2026-05-30 PM, landed) — split from Stage 1 - -Stage 1's deliverables split into two slices for shipping cadence: - -**Stage 1a (this PR, landed)** — Infrastructure + soundness-gap witness: -- `tfuneff_lambda_free : expr -> bool` Fixpoint in `formal/Syntax.v`. Conservative leaf-only predicate: `false` on every `ELam`, `true` elsewhere; propagates compositionally through compound forms via `andb`. -- `formal/Counterexample_L2_nested.v` — five `Qed` lemmas (`v_typed_at_empty`, `outer_typed`, `e_before_typed`, `e_step`, `e_after_untypable`) mechanising the nested-TFunEff soundness gap. Configuration: `outer = ELam T_v (ELam (TBase TUnit) (EVar 1))` with inner `R_in_inner = [r2]`; `v = ELam TUnit EUnit` at `TFunEff TUnit TUnit [] []`. Post-β `e_after = ELam TUnit v` cannot retype the body at `[r2] ⊄ [] = R_in_v`. Sibling artifact to `Counterexample_L2.v`: together the two files justify the **two-condition** preservation_l2 statement Stage 1 ships (P1 = `tfuneff_lambda_free ebody`, P2 = `regions_introduced_by ebody ⊆ R_in_v`). -- Wired into `_CoqProject` after `Counterexample_L2.v`. -- Zero new admits / axioms (Print Assumptions: all five lemmas Closed under the global context). - -**Stage 1b (follow-up issue, deferred)** — Substitution lemma + preservation wrapper: -- `subst_typing_gen_l1_m_tfuneff` Qed in `formal/Semantics_L1.v` mirroring `subst_typing_gen_l1_m_ground_nonlinear` (~300 lines); inner `T_Lam_L1_*_Eff` cases exfalso via `tfuneff_lambda_free`; direct (P1, P2) hypothesis form. -- `preservation_l2_app_eff_beta_tfuneff_l1` + L2 wrapper Qed in `formal/TypingL2.v`. -- The structural blocker requiring deferral: the substitution lemma's compound rule cases (T_Let_L1, T_LetLin_L1, T_Case_L1_*) need to retype the substituent value `v` at a different G (post-e1 used-flag updates). Phase 2's analog uses `ground_nonlinear_retype_l1_m` (R-poly AND G-poly); Phase 3b's `tfuneff_lambda_retype_l1_m` preserves G. The Stage 1b machinery needs EITHER (a) a closed-value G-polymorphism helper (`closed_value_typing_G_poly_l1_m` — provable via inversion on T_Lam_L1_*_Eff formation rules but lengthy), OR (b) a G-flag-polymorphic retype variant. Either path is its own sub-deliverable. - -### Phase 4: close `preservation_l2` β-case using Phases 1-3 - -With the substitution machinery in place, the T_App_L2_Eff β-case in `preservation_l2` closes by: -1. Inversion on the L2 derivation → L1 derivation of `T_Lam_L1_*_Eff`. -2. Inversion on the L1 derivation → body typing at R_in + side condition. -3. Apply `value_R_G_preserving_l1` to argument value → R_in = R, G'' = G. -4. Case-split on `is_linear_ty T1`: - - Linear: use existing `subst_typing_gen_l1_m`. - - Ground non-linear: use Phase 2 lemma. - - TFunEff non-linear: use Phase 3 lemma. - - Other non-linear: deferred (compound values; see Phase 5). -5. `L2_lift_l1` wrap. - -### Phase 5 (deferred): compound non-linear values - -`EPair` / `EInl` / `EInr` / `EEcho` of non-linear components. Sub-component analysis. May require additional retype machinery. Realistically multiple sessions of work. - -## Phase 4c addendum (2026-05-30) — conditional preservation_l2 for TFunEff β - -Prototyping Phase 4c on paper (after Phase 4a / 4b landed at PRs #228 / #233) revealed a structural soundness gap that requires a **conditional** preservation_l2 statement for the TFunEff β-case. `formal/Counterexample_L2.v` mechanises the witness. - -### The obstacle (mechanised in `Counterexample_L2.v`) - -For any TFunEff lambda body that introduces a fresh region via `ERegion` AND references the substituted variable inside that region scope, β-reduction does **not** preserve typing: - -``` -T1_inner = TFunEff TUnit TUnit [] [] (* substituee type, R_in_v = [] *) -outer = ELam T1_inner (ERegion r2 (EVar 0)) (* body introduces fresh r2 *) -v2 = ELam TUnit EUnit (* a value of type T1_inner *) -e_before = EApp outer v2 (* well-typed via T_App_L2_Eff at R = [] *) -e_after = ERegion r2 v2 (* β-result: subst 0 v2 (ERegion r2 (EVar 0)) *) -``` - -`e_before` types (Qed: `e_before_typed`); `e_step` reduces it to `e_after` (Qed: `e_step`); `e_after` does not type at the same outer type (Qed: `e_after_untypable`). The mechanism: T_Region_L1's `~ In r (free_regions T)` premise prevents the fresh `r` from being in `R_in_v` (since `R_in_v ⊆ free_regions(TFunEff)`). After β-substitution, the inner expression becomes a TFunEff value that must re-type at `r :: R`, requiring `r ∈ R_in_v` (false). - -### Resolution: Phase 4c ships **conditionally** - -Preservation_l2 for the T_App_L2_Eff β-case holds **only** for programs satisfying: - -``` -regions_introduced_by(ebody) ⊆ R_in_v -``` - -where `regions_introduced_by` is the `Fixpoint` already landed in `Syntax.v` (PR #230), `ebody` is the outer lambda's body, and `R_in_v` is the substituee's TFunEff input region env. - -The Phase 3b substitution lemma (`subst_typing_gen_l1_m_tfuneff`) ships with this precondition; the Phase 4c β-case wrapper propagates it. Programs not satisfying the precondition are a **documented soundness-gap subclass** — they witness the same fundamental "scoped resource cannot escape its scope" limitation that motivated the four-layer redesign in the first place. - -### Three resolution paths (broader than original (a)/(b)/(c) options) - -1. **Conditional preservation_l2 (recommended)**: Phase 3b lemma takes the precondition; Phase 4c β-case requires it; programs outside form a documented soundness-gap class. This is what `Counterexample_L2.v` justifies. Aligns with the legacy `Counterexample.v` precedent (preservation holds modulo a structural constraint that legitimate programs satisfy). -2. **Region-polymorphic TFunEff**: change the type system so `TFunEff T1 T2 R_in R_out` permits implicit region extension at use sites. Major type-system change; defers to a future redesign. -3. **L2 region-transfer combinator**: add an L2 typing rule that explicitly transfers a fresh region into a TFunEff lambda's R_in for the duration of a scope. Adds L2 expressiveness; defers to a future PR. - -### What ships in the Phase 4c PR - -- `formal/Counterexample_L2.v` — five Qed lemmas mechanising the soundness gap. -- This addendum to `SUBST-LEMMA-GENERALIZATION-DESIGN.md`. -- STATE.a2ml refresh to reflect the conditional path. - -Phase 3b implementation (the ~400-line `subst_typing_gen_l1_m_tfuneff` lemma body) and Phase 4c β-case wrapper remain follow-up work — but the design constraint they must satisfy is now mechanically witnessed. - -## What this session ships - -This design document only. No code changes. STATE.a2ml shifts `next_action` to "Phase 1: implement `ground_nonlinear_retype_l1_m` per `formal/SUBST-LEMMA-GENERALIZATION-DESIGN.md`". - -## Owner-directive compliance check - -Per `CLAUDE.md` 2026-05-27: - -- ✅ Does not propose closing legacy `preservation` in `Semantics.v` (provably false). -- ✅ Does not extend `Semantics.v` with closure-support lemmas. -- ✅ Does not close residual `Semantics_L1.v` axioms via proof tricks — Phase 1-2 add NEW infrastructure to `Semantics_L1.v` orthogonal to legacy admits. -- ✅ Does not follow pre-2026-05-26 closure plans. -- ✅ Does not patch legacy `Typing.v`. -- ✅ Reads `PRESERVATION-DESIGN.md` first (specifically §5.1 lines 468-474 — the L1-intro / L2-elim design vision endorsing T_App_L2_Eff at L2). -- ✅ Works per-layer (L1 infrastructure → L2 preservation closure). -- ✅ Escalates before patching (this design doc IS the escalation). - -Anti-pattern detector (per `CLAUDE.md` §"Anti-pattern detector"): - -- ✅ No sibling-region-disjointness side conditions proposed. -- ✅ No region-weakening predicates indexed on syntactic shape (the proposed retype lemmas are indexed on TYPE shape, not syntactic shape — `is_ground_nonlinear_ty` is a type predicate). -- ✅ No admit-shuffling between `Semantics.v` and a new lemma. -- ✅ No proposal to close `Theorem preservation` in `Semantics.v` to `Qed.`. -- ✅ No new `Axiom` declarations. - -## Open design questions for owner - -1. **`is_ground_nonlinear_ty` predicate placement**: `Syntax.v` (near `is_linear_ty`) or `Semantics_L1.v` (near the lemma that uses it)? Recommendation: `Syntax.v` for symmetry with `is_linear_ty`. - -2. **Phase 3 scope**: TFunEff lambdas only, or also TFun lambdas with the body-R-rigidity gap honestly admitted? Recommendation: TFunEff only (TFun is the legacy slice 4b debt; not in scope for this initiative). - -3. **Phase 5 priority**: should compound non-linear values block `preservation_l2` closure, or can `preservation_l2` Qed with the compound-value case admitted? Recommendation: defer Phase 5; `preservation_l2` can close conditionally on a `nonlinear_compound_substitutable T` predicate that returns true for ground + TFunEff and false otherwise. The predicate's `true` cases let preservation_l2 fire; the `false` cases vacuously discharge or admit at the predicate-false branch. - -4. **Sibling vs case-split**: Phase 2 ships as a parallel lemma (`subst_typing_gen_l1_m_ground_nonlinear`) rather than folding case-split into the existing lemma. Avoids breaking ~30 Qed downstreams of `subst_typing_gen_l1_m`. Confirmed. - -## References - -- `formal/Semantics_L1.v:1358-1656` — `subst_typing_gen_l1_m` proof body. -- `formal/Semantics_L1.v:916-933` — `linear_value_is_loc_l1` canonical-form extractor. -- `formal/Semantics_L1.v:1168-1235` — `loc_retype_at_R_l1_m` (the lemma to parallel for non-linear values). -- `formal/Semantics_L1.v:1708-1713` — body-R-rigidity comment in preservation_l1. -- `formal/TypingL1.v:221-229` — `T_Lam_L1_Linear_Eff` / `T_Lam_L1_Affine_Eff` (TFunEff lambdas with R_in side condition). -- `formal/TypingL2.v` post-PR #211 — `preservation_l2_via_l1` + doc block stating the full preservation_l2 goal. -- `formal/PRESERVATION-DESIGN.md` §5.1 lines 468-474 — load-bearing design quote. -- `CLAUDE.md` owner directive 2026-05-27 — preservation-work boundaries. -- PR #209 — T_App_L2_Eff constructor. -- PR #211 — preservation_l2_via_l1. -- PR #212 — STATE shift after PR #211. diff --git a/llm-warmup-dev.adoc b/llm-warmup-dev.adoc new file mode 100644 index 00000000..04add474 --- /dev/null +++ b/llm-warmup-dev.adoc @@ -0,0 +1,19 @@ +== LLM Warmup — ephapax (Developer) + +=== What is ephapax? + +See README.adoc for overview. + +=== Key Commands + +* `+just setup+` — set up development environment +* `+just build+` — build the project +* `+just test+` — run tests +* `+just doctor+` — diagnose issues +* `+just heal+` — attempt auto-repair + +=== Quick Context + +* License: MPL-2.0 (code), CC-BY-SA-4.0 (documentation) +* Part of hyperpolymath ecosystem +* See EXPLAINME.adoc for architecture diff --git a/llm-warmup-dev.md b/llm-warmup-dev.md deleted file mode 100644 index 596b2615..00000000 --- a/llm-warmup-dev.md +++ /dev/null @@ -1,16 +0,0 @@ -# LLM Warmup — ephapax (Developer) - -## What is ephapax? -See README.adoc for overview. - -## Key Commands -- `just setup` — set up development environment -- `just build` — build the project -- `just test` — run tests -- `just doctor` — diagnose issues -- `just heal` — attempt auto-repair - -## Quick Context -- License: MPL-2.0 (code), CC-BY-SA-4.0 (documentation) -- Part of hyperpolymath ecosystem -- See EXPLAINME.adoc for architecture diff --git a/llm-warmup-user.adoc b/llm-warmup-user.adoc new file mode 100644 index 00000000..67a517f0 --- /dev/null +++ b/llm-warmup-user.adoc @@ -0,0 +1,19 @@ +== LLM Warmup — ephapax (User) + +=== What is ephapax? + +See README.adoc for overview. + +=== Key Commands + +* `+just setup+` — set up development environment +* `+just build+` — build the project +* `+just test+` — run tests +* `+just doctor+` — diagnose issues +* `+just heal+` — attempt auto-repair + +=== Quick Context + +* License: MPL-2.0 (code), CC-BY-SA-4.0 (documentation) +* Part of hyperpolymath ecosystem +* See EXPLAINME.adoc for architecture diff --git a/llm-warmup-user.md b/llm-warmup-user.md deleted file mode 100644 index 4ab59a85..00000000 --- a/llm-warmup-user.md +++ /dev/null @@ -1,16 +0,0 @@ -# LLM Warmup — ephapax (User) - -## What is ephapax? -See README.adoc for overview. - -## Key Commands -- `just setup` — set up development environment -- `just build` — build the project -- `just test` — run tests -- `just doctor` — diagnose issues -- `just heal` — attempt auto-repair - -## Quick Context -- License: MPL-2.0 (code), CC-BY-SA-4.0 (documentation) -- Part of hyperpolymath ecosystem -- See EXPLAINME.adoc for architecture diff --git a/site/index.adoc b/site/index.adoc new file mode 100644 index 00000000..b86d2668 --- /dev/null +++ b/site/index.adoc @@ -0,0 +1,24 @@ +== Ephapax + +The public web home for this project is +https://ephapax.org[ephapax.org]. + +A dyadic language - one part affine, the other linear - as a type system +with safe memory management targeting WebAssembly. + +Ephapax uses a compact, statement-delimited block syntax. Newlines act +as statement separators (equivalent to ;). + +=== Project Links + +* Website: https://ephapax.org[ephapax.org] +* Source: https://github.com/hyperpolymath/ephapax +* README: +https://github.com/hyperpolymath/ephapax/blob/main/README.adoc[project +overview] +* Docs: +https://github.com/hyperpolymath/ephapax/tree/main/docs[documentation +directory] + +This page is a lightweight landing point for the repository and will +grow with the project. diff --git a/site/index.md b/site/index.md deleted file mode 100644 index 6ffa5d67..00000000 --- a/site/index.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Ephapax -date: 2026-03-31 ---- - -# Ephapax - -The public web home for this project is [ephapax.org](https://ephapax.org). - -A dyadic language - one part affine, the other linear - as a type system with safe memory management targeting WebAssembly. - -Ephapax uses a compact, statement-delimited block syntax. Newlines act as statement separators (equivalent to ;). - -## Project Links - -- Website: [ephapax.org](https://ephapax.org) -- Source: [https://github.com/hyperpolymath/ephapax](https://github.com/hyperpolymath/ephapax) -- README: [project overview](https://github.com/hyperpolymath/ephapax/blob/main/README.adoc) -- Docs: [documentation directory](https://github.com/hyperpolymath/ephapax/tree/main/docs) - -This page is a lightweight landing point for the repository and will grow with the project. diff --git a/spec/SPEC.adoc b/spec/SPEC.adoc new file mode 100644 index 00000000..9c38eec3 --- /dev/null +++ b/spec/SPEC.adoc @@ -0,0 +1,3 @@ +== Ephapax Language Specification + +*Version*: 0.1.0-draft *Date*: 2025-12-17 *Status*: Draft diff --git a/spec/SPEC.md b/spec/SPEC.md deleted file mode 100644 index 10249e5b..00000000 --- a/spec/SPEC.md +++ /dev/null @@ -1,8 +0,0 @@ - - - -# Ephapax Language Specification - -**Version**: 0.1.0-draft -**Date**: 2025-12-17 -**Status**: Draft diff --git a/spec/ephapax-spec.md b/spec/ephapax-spec.adoc similarity index 55% rename from spec/ephapax-spec.md rename to spec/ephapax-spec.adoc index 72175a42..2a94b923 100644 --- a/spec/ephapax-spec.md +++ b/spec/ephapax-spec.adoc @@ -1,75 +1,84 @@ -# Ephapax Complete Language Specification v0.1 +== Ephapax Complete Language Specification v0.1 - - - +=== Purpose -## Purpose +This document specifies Ephapax (ἐφάπαξ — "`once for all`"), a dyadic +programming language combining: - *Affine types* (values used at most +once — implicit drop permitted) - *Linear types* (values used exactly +once — implicit drop is a compile error) - *Region-based memory +management* (scoped arenas, no GC) - *WASM-first* compilation (no +garbage collector required) -This document specifies Ephapax (ἐφάπαξ — "once for all"), a dyadic programming -language combining: -- **Affine types** (values used at most once — implicit drop permitted) -- **Linear types** (values used exactly once — implicit drop is a compile error) -- **Region-based memory management** (scoped arenas, no GC) -- **WASM-first** compilation (no garbage collector required) +The dyadic principle: both disciplines coexist at per-binding +granularity. -The dyadic principle: both disciplines coexist at per-binding granularity. +Ephapax’s type system has four orthogonal disciplines: *L1* region +capabilities, *L2* structural modality (linear/affine), *L3* +irreversibility residue (Echo Types — planned), and *L4* dyadic +interaction mode (project-level declaration). This specification is +normative for L1 and L2 and forward-looking for L3 and L4. See +link:../docs/vision/EPHAPAX-VISION.adoc[`+docs/vision/EPHAPAX-VISION.adoc+`] +for the dyad framing and +link:../formal/PRESERVATION-DESIGN.md[`+formal/PRESERVATION-DESIGN.md+`] +for the per-layer design. -Ephapax's type system has four orthogonal disciplines: **L1** region capabilities, **L2** structural modality (linear/affine), **L3** irreversibility residue (Echo Types — planned), and **L4** dyadic interaction mode (project-level declaration). This specification is normative for L1 and L2 and forward-looking for L3 and L4. See [`docs/vision/EPHAPAX-VISION.adoc`](../docs/vision/EPHAPAX-VISION.adoc) for the dyad framing and [`formal/PRESERVATION-DESIGN.md`](../formal/PRESERVATION-DESIGN.md) for the per-layer design. +''''' ---- +== PART 1: LEXICAL GRAMMAR -# PART 1: LEXICAL GRAMMAR +=== 1.1 Character Classes -## 1.1 Character Classes - -```ebnf +[source,ebnf] +---- letter = 'a'..'z' | 'A'..'Z' ; digit = '0'..'9' ; alpha_num = letter | digit | '_' ; hex_digit = digit | 'a'..'f' | 'A'..'F' ; bin_digit = '0' | '1' ; oct_digit = '0'..'7' ; -``` +---- -## 1.2 Whitespace and Comments +=== 1.2 Whitespace and Comments -```ebnf +[source,ebnf] +---- whitespace = ' ' | '\t' | '\n' | '\r' ; line_comment = '//' { any_char - '\n' } '\n' ; skip = whitespace | line_comment ; -``` +---- -## 1.3 Identifiers +=== 1.3 Identifiers -```ebnf +[source,ebnf] +---- lower_ident = ('a'..'z' | '_') { alpha_num } ; upper_ident = ('A'..'Z') { alpha_num } ; ident = lower_ident ; type_ident = upper_ident ; module_path = upper_ident { '.' upper_ident } ; -``` +---- -## 1.4 Keywords +=== 1.4 Keywords -``` +.... fn let let! in region match if else type module import return true false copy borrow move drop -``` +.... -## 1.5 Operators +=== 1.5 Operators -``` +.... + - * / % == != < > <= >= && || ! & @ :: -> => : ; = | , . .. ( ) { } [ ] -``` +.... -## 1.6 Literals +=== 1.6 Literals -```ebnf +[source,ebnf] +---- integer = digit { digit | '_' } | '0x' hex_digit { hex_digit | '_' } | '0b' bin_digit { bin_digit | '_' } @@ -89,15 +98,16 @@ escape_seq = '\\' ( 'n' | 'r' | 't' | '\\' | '"' | '\'' char = '\'' ( any_char - '\'' - '\\' | escape_seq ) '\'' ; unit = '(' ')' ; -``` +---- ---- +''''' -# PART 2: SYNTAX +== PART 2: SYNTAX -## 2.1 Top-Level Declarations +=== 2.1 Top-Level Declarations -```ebnf +[source,ebnf] +---- program = { declaration } ; declaration = fn_decl @@ -119,11 +129,12 @@ type_decl = 'type' upper_ident [ type_params ] '=' type_expr ; module_decl = 'module' module_path block ; import_decl = 'import' module_path ; -``` +---- -## 2.2 Expressions +=== 2.2 Expressions -```ebnf +[source,ebnf] +---- expr = let_expr | let_linear_expr | region_expr @@ -172,32 +183,35 @@ block = '{' { stmt } [ expr ] '}' ; stmt = expr ';' | let_expr | let_linear_expr ; -``` +---- -## 2.3 Region-Specific Syntax +=== 2.3 Region-Specific Syntax -```ebnf +[source,ebnf] +---- (* Region-scoped allocation: value allocated in region r *) region_alloc = expr '@' ident '(' args ')' ; (* Example: String.new@r("hello") *) (* The @r is the region annotation — binds value's lifetime to region r *) -``` +---- -## 2.4 Patterns +=== 2.4 Patterns -```ebnf +[source,ebnf] +---- pattern = '_' (* wildcard *) | ident (* variable binding *) | literal (* literal pattern *) | upper_ident '(' pattern { ',' pattern } ')' (* constructor *) | '(' pattern ',' pattern { ',' pattern } ')' (* tuple *) ; -``` +---- -## 2.5 Type Expressions +=== 2.5 Type Expressions -```ebnf +[source,ebnf] +---- type_expr = type_ident (* named type *) | type_ident '<' type_expr { ',' type_expr } '>' (* generic *) | '(' type_expr { ',' type_expr } ')' (* tuple *) @@ -205,109 +219,110 @@ type_expr = type_ident (* named type *) | '&' type_expr (* borrow/reference *) | type_expr '@' ident (* region-scoped type *) | 'i32' | 'i64' | 'f32' | 'f64' | 'bool' | 'String' | '()' ; -``` +---- ---- +''''' -# PART 3: TYPE SYSTEM +== PART 3: TYPE SYSTEM -## 3.1 Qualifiers +=== 3.1 Qualifiers -Every binding has a qualifier: -- **Affine** (`let`): value may be used at most once. Unused values are - implicitly dropped at scope exit. -- **Linear** (`let!`): value must be used exactly once. Unused values are - a compile-time error. +Every binding has a qualifier: - *Affine* (`+let+`): value may be used +at most once. Unused values are implicitly dropped at scope exit. - +*Linear* (`+let!+`): value must be used exactly once. Unused values are +a compile-time error. -## 3.2 Context Splitting +=== 3.2 Context Splitting -When an expression has sub-expressions, the typing context is split: -- Linear bindings go to exactly one sub-expression. -- Affine bindings go to at most one sub-expression (may be dropped). +When an expression has sub-expressions, the typing context is split: - +Linear bindings go to exactly one sub-expression. - Affine bindings go +to at most one sub-expression (may be dropped). -## 3.3 Branch Consistency +=== 3.3 Branch Consistency -Both branches of `if`/`match` must consume the same linear bindings. +Both branches of `+if+`/`+match+` must consume the same linear bindings. -## 3.4 Region-Linear Fusion +=== 3.4 Region-Linear Fusion The interaction between regions and qualifiers is the core innovation. -Three rules govern region exit, and they are **orthogonal** to qualifiers: +Three rules govern region exit, and they are *orthogonal* to qualifiers: -### 3.4.1 NoRegionInType (No Escape) +==== 3.4.1 NoRegionInType (No Escape) The return type of a region block must not reference the region. -`Ty::references_region(r)` recursively checks all type constructors: +`+Ty::references_region(r)+` recursively checks all type constructors: String, Region, Ref, Fun, Prod, Sum, List, Tuple, Borrow. -This rule is **qualifier-independent**: it applies identically to -affine and linear bindings. One implementation, both modes. +This rule is *qualifier-independent*: it applies identically to affine +and linear bindings. One implementation, both modes. -``` +.... region r: let s = String.new@r("hello") s // ERROR: String@r references region r — cannot escape -``` +.... -### 3.4.2 AllLinearsConsumed +==== 3.4.2 AllLinearsConsumed -At region exit, all **linear** variables bound within the region must -have been consumed. **Affine** variables may be implicitly dropped — -the region's arena deallocator frees their memory. +At region exit, all *linear* variables bound within the region must have +been consumed. *Affine* variables may be implicitly dropped — the +region’s arena deallocator frees their memory. -``` +.... region r: let! conn = Db.connect@r(...) // linear let buf = Buffer.new@r(1024) // affine Db.close(conn) // linear consumed — OK // buf not consumed — OK (affine, arena handles it) 42 -``` +.... -### 3.4.3 Region Safety +==== 3.4.3 Region Safety -After exit, the region is no longer active. Any attempt to allocate -in it (`String.new@r(...)`) or reference it is a type error. +After exit, the region is no longer active. Any attempt to allocate in +it (`+String.new@r(...)+`) or reference it is a type error. -### 3.4.4 Orthogonality Lemma +==== 3.4.4 Orthogonality Lemma -The region rules (3.4.1, 3.4.3) never inspect the qualifier. -The qualifier rules (3.1, 3.2, 3.3) never inspect the region. -They compose without interaction. This means: -- Adding region support doesn't change affine/linear enforcement. -- Changing a binding's qualifier doesn't change region escape checking. -- The region system only needs to be implemented once for both modes. +The region rules (3.4.1, 3.4.3) never inspect the qualifier. The +qualifier rules (3.1, 3.2, 3.3) never inspect the region. They compose +without interaction. This means: - Adding region support doesn’t change +affine/linear enforcement. - Changing a binding’s qualifier doesn’t +change region escape checking. - The region system only needs to be +implemented once for both modes. -## 3.5 Borrowing +=== 3.5 Borrowing -`&x` borrows `x` without consuming it. The borrow's lifetime is bounded -by `x`'s region. Borrows are always read-only. +`+&x+` borrows `+x+` without consuming it. The borrow’s lifetime is +bounded by `+x+`’s region. Borrows are always read-only. ---- +''''' -# PART 4: SEMANTICS +== PART 4: SEMANTICS -## 4.1 Region Semantics +=== 4.1 Region Semantics -- `region r: { ... }` creates an arena allocator `r`. -- `Foo.new@r(...)` allocates in `r`. -- At region exit, all memory in `r` is freed in bulk. -- Values in `r` are inaccessible after exit (enforced by type system). +* `+region r: { ... }+` creates an arena allocator `+r+`. +* `+Foo.new@r(...)+` allocates in `+r+`. +* At region exit, all memory in `+r+` is freed in bulk. +* Values in `+r+` are inaccessible after exit (enforced by type system). -## 4.2 Linear Semantics +=== 4.2 Linear Semantics -- A linear binding (`let!`) must appear exactly once in the continuation. -- If unused: compile error "linear variable not consumed". -- If used twice: compile error "linear variable already consumed". +* A linear binding (`+let!+`) must appear exactly once in the +continuation. +* If unused: compile error "`linear variable not consumed`". +* If used twice: compile error "`linear variable already consumed`". -## 4.3 Affine Semantics +=== 4.3 Affine Semantics -- An affine binding (`let`) may appear at most once. -- If unused: silently dropped (weakening rule applies). -- If used twice: compile error "affine variable already consumed". +* An affine binding (`+let+`) may appear at most once. +* If unused: silently dropped (weakening rule applies). +* If used twice: compile error "`affine variable already consumed`". -## 4.4 Evaluation Order +=== 4.4 Evaluation Order -- Left-to-right evaluation of arguments. -- Eager/strict evaluation (no laziness). -- Block expressions evaluate sequentially; final expression is the result. +* Left-to-right evaluation of arguments. +* Eager/strict evaluation (no laziness). +* Block expressions evaluate sequentially; final expression is the +result. diff --git a/src/ephapax-vram-cache/README.md b/src/ephapax-vram-cache/README.adoc similarity index 65% rename from src/ephapax-vram-cache/README.md rename to src/ephapax-vram-cache/README.adoc index b94aec3b..7f4e8d8a 100644 --- a/src/ephapax-vram-cache/README.md +++ b/src/ephapax-vram-cache/README.adoc @@ -1,36 +1,42 @@ -# ephapax-vram-cache +== ephapax-vram-cache GPU-accelerated IR caching for the Ephapax compiler. -## Overview +=== Overview -This crate provides VRAM-backed caching for intermediate representation (IR) data during Ephapax compilation. By storing frequently accessed IR in GPU memory, we achieve **10-100x speedup** for incremental builds. +This crate provides VRAM-backed caching for intermediate representation +(IR) data during Ephapax compilation. By storing frequently accessed IR +in GPU memory, we achieve *10-100x speedup* for incremental builds. -## Features +=== Features -- **CUDA Support**: Leverage NVIDIA GPUs for ultra-fast caching -- **WebGPU Support**: Cross-platform GPU acceleration -- **CPU Fallback**: Automatic fallback to RAM cache if no GPU available -- **LRU Eviction**: Intelligent cache management with least-recently-used eviction -- **Zero-copy**: Direct GPU memory access without serialization overhead -- **Thread-safe**: Safe concurrent access (planned) +* *CUDA Support*: Leverage NVIDIA GPUs for ultra-fast caching +* *WebGPU Support*: Cross-platform GPU acceleration +* *CPU Fallback*: Automatic fallback to RAM cache if no GPU available +* *LRU Eviction*: Intelligent cache management with least-recently-used +eviction +* *Zero-copy*: Direct GPU memory access without serialization overhead +* *Thread-safe*: Safe concurrent access (planned) -## Performance +=== Performance -| Operation | CPU Cache | VRAM Cache | Speedup | -|-----------|-----------|------------|---------| -| Cache hit (1KB) | 100µs | 1µs | **100x** | -| Cache hit (10KB) | 200µs | 2µs | **100x** | -| Cache hit (100KB) | 500µs | 10µs | **50x** | -| Insert (1KB) | 150µs | 20µs | **7.5x** | +[cols=",,,",options="header",] +|=== +|Operation |CPU Cache |VRAM Cache |Speedup +|Cache hit (1KB) |100µs |1µs |*100x* +|Cache hit (10KB) |200µs |2µs |*100x* +|Cache hit (100KB) |500µs |10µs |*50x* +|Insert (1KB) |150µs |20µs |*7.5x* +|=== -*Benchmarks run on NVIDIA Quadro M2000M (4GB VRAM)* +_Benchmarks run on NVIDIA Quadro M2000M (4GB VRAM)_ -## Usage +=== Usage -### Basic Example +==== Basic Example -```rust +[source,rust] +---- use ephapax_vram_cache::VramCache; fn main() -> Result<(), Box> { @@ -61,11 +67,12 @@ fn main() -> Result<(), Box> { Ok(()) } -``` +---- -### Integration with ephapax-cli +==== Integration with ephapax-cli -```rust +[source,rust] +---- use ephapax_vram_cache::VramCache; use std::sync::{Arc, Mutex}; @@ -98,44 +105,49 @@ impl Compiler { Ok(ir) } } -``` +---- -## Configuration +=== Configuration -### Cache Size Recommendations +==== Cache Size Recommendations Based on your system RAM and VRAM: -| VRAM Available | Recommended Cache Size | Expected Cached Modules | -|----------------|------------------------|-------------------------| -| 2GB | 256MB (12%) | ~50-100 modules | -| 4GB | 512MB (12%) | ~100-200 modules | -| 8GB+ | 1-2GB (12-25%) | ~200-500 modules | +[cols=",,",options="header",] +|=== +|VRAM Available |Recommended Cache Size |Expected Cached Modules +|2GB |256MB (12%) |~50-100 modules +|4GB |512MB (12%) |~100-200 modules +|8GB+ |1-2GB (12-25%) |~200-500 modules +|=== -### Feature Flags +==== Feature Flags -Enable GPU support in `Cargo.toml`: +Enable GPU support in `+Cargo.toml+`: -```toml +[source,toml] +---- [dependencies] ephapax-vram-cache = { version = "0.1", features = ["cuda"] } # or ephapax-vram-cache = { version = "0.1", features = ["webgpu"] } # or fallback to RAM ephapax-vram-cache = { version = "0.1", features = ["cpu-fallback"] } -``` +---- -## Benchmarks +=== Benchmarks Run benchmarks: -```bash +[source,bash] +---- cargo bench --package ephapax-vram-cache -``` +---- -## Architecture +=== Architecture -```text +[source,text] +---- ┌─────────────────────────────────────────┐ │ Ephapax Compiler │ │ │ @@ -174,11 +186,12 @@ cargo bench --package ephapax-vram-cache │ ▼ WASM Binary -``` +---- -## GPU Memory Layout +=== GPU Memory Layout -```text +[source,text] +---- GPU Memory (4GB Quadro M2000M) ├─ IR Cache (512MB) - LRU managed │ ├─ main.eph.ir (2MB) @@ -188,53 +201,62 @@ GPU Memory (4GB Quadro M2000M) ├─ Parser Tokens (256MB) - Future ├─ Type Constraints (256MB) - Future └─ Free Space (3GB) -``` - -## Troubleshooting - -### "No GPU available" error - -1. Check NVIDIA drivers: - ```bash - nvidia-smi - ``` - -2. Verify CUDA installation: - ```bash - nvcc --version - ``` - -3. Fall back to CPU cache: - ```toml - [dependencies] - ephapax-vram-cache = { version = "0.1", features = ["cpu-fallback"] } - ``` +---- + +=== Troubleshooting + +==== "`No GPU available`" error + +[arabic] +. Check NVIDIA drivers: ++ +[source,bash] +---- +nvidia-smi +---- +. Verify CUDA installation: ++ +[source,bash] +---- +nvcc --version +---- +. Fall back to CPU cache: ++ +[source,toml] +---- +[dependencies] +ephapax-vram-cache = { version = "0.1", features = ["cpu-fallback"] } +---- -### Poor cache hit rate +==== Poor cache hit rate -1. Increase cache size -2. Check if source files change frequently -3. Monitor with stats: - ```rust - let stats = cache.stats(); - eprintln!("Hit rate: {:.1}%", stats.hit_rate() * 100.0); - eprintln!("Evictions: {}", stats.evictions); - ``` +[arabic] +. Increase cache size +. Check if source files change frequently +. Monitor with stats: ++ +[source,rust] +---- +let stats = cache.stats(); +eprintln!("Hit rate: {:.1}%", stats.hit_rate() * 100.0); +eprintln!("Evictions: {}", stats.evictions); +---- -## Future Work +=== Future Work -- [ ] Multi-GPU support -- [ ] Distributed caching across machines -- [ ] GPU parser acceleration -- [ ] GPU type checker parallelization -- [ ] WebGPU backend for cross-platform support +* [ ] Multi-GPU support +* [ ] Distributed caching across machines +* [ ] GPU parser acceleration +* [ ] GPU type checker parallelization +* [ ] WebGPU backend for cross-platform support -## License +=== License MPL-2.0 -## Related +=== Related -- [ephapax](https://github.com/hyperpolymath/ephapax) - The Ephapax language -- [cudarc](https://github.com/coreylowman/cudarc) - CUDA bindings for Rust -- [wgpu](https://github.com/gfx-rs/wgpu) - WebGPU implementation +* https://github.com/hyperpolymath/ephapax[ephapax] - The Ephapax +language +* https://github.com/coreylowman/cudarc[cudarc] - CUDA bindings for Rust +* https://github.com/gfx-rs/wgpu[wgpu] - WebGPU implementation diff --git a/tests/fuzz/FUZZING_GUIDE.adoc b/tests/fuzz/FUZZING_GUIDE.adoc new file mode 100644 index 00000000..0524a27c --- /dev/null +++ b/tests/fuzz/FUZZING_GUIDE.adoc @@ -0,0 +1,148 @@ +== Fuzzing Guide for Ephapax + +This guide explains how to use the fuzz testing infrastructure for +Ephapax. + +=== Quick Start + +[source,bash] +---- +# From repository root +./tests/fuzz/run_fuzz.sh +---- + +This will: 1. Install cargo-fuzz if needed 2. Build the fuzz targets 3. +Run both fuzzers for 60 seconds each + +=== Fuzz Targets + +==== 1. Parser Fuzzer (`+parse_fuzzer+`) + +*Location*: `+tests/fuzz/src/parse_fuzzer/lib.rs+` + +*Purpose*: Test parser robustness against arbitrary input + +*Input*: Raw bytes (converted to UTF-8) + +*Oracle*: Parser should not panic, hang, or cause memory issues + +*Example usage*: + +[source,bash] +---- +cd tests/fuzz +cargo fuzz run parse_fuzzer -- -max_total_time=300 +---- + +==== 2. Type Checker Fuzzer (`+typecheck_fuzzer+`) + +*Location*: `+tests/fuzz/src/typecheck_fuzzer/lib.rs+` + +*Purpose*: Test type checker correctness on valid ASTs + +*Input*: Raw bytes → UTF-8 → Parser → Type checker + +*Oracle*: Type checker should not panic on any valid AST + +*Example usage*: + +[source,bash] +---- +cd tests/fuzz +cargo fuzz run typecheck_fuzzer -- -max_total_time=300 +---- + +=== Corpus Management + +The seed corpus is in `+tests/fuzz/corpus/+`: + +[source,bash] +---- +# Add a new test case to parser corpus +cp my_test.eph tests/fuzz/corpus/parse_fuzzer/ + +# Add a new test case to type checker corpus +cp my_test.eph tests/fuzz/corpus/typecheck_fuzzer/ +---- + +=== Advanced Usage + +==== Run with specific options + +[source,bash] +---- +# Limit input size to 1KB +cargo fuzz run parse_fuzzer -- -max_len=1024 + +# Run with 4 workers +cargo fuzz run parse_fuzzer -- -jobs=4 + +# Run for 10 minutes +cargo fuzz run parse_fuzzer -- -max_total_time=600 +---- + +==== Minimize a test case + +[source,bash] +---- +cargo fuzz tmin parse_fuzzer tests/fuzz/artifacts/parse_fuzzer/crash-* +---- + +==== View coverage + +[source,bash] +---- +cargo fuzz coverage parse_fuzzer +---- + +=== CI Integration + +Add to your CI workflow: + +[source,yaml] +---- +- name: Fuzz Test + run: | + cd tests/fuzz + cargo fuzz run parse_fuzzer -- -max_total_time=60 + cargo fuzz run typecheck_fuzzer -- -max_total_time=60 +---- + +=== Safety Guidelines + +[arabic] +. *Run in isolated environments*: Fuzzers can consume significant +resources +. *Set time limits*: Always use `+-max_total_time+` +. *Monitor resource usage*: Fuzzing can use lots of CPU and memory +. *Start with small inputs*: Use `+-max_len=1024+` initially +. *Review crashes carefully*: Not all crashes are security issues + +=== Troubleshooting + +==== "`No targets specified`" + +Make sure you’re in the `+tests/fuzz+` directory and have run +`+cargo fuzz build+`. + +==== Build failures + +Ensure all workspace dependencies are built: + +[source,bash] +---- +cd .. +cargo build --workspace +cd tests/fuzz +---- + +==== Performance issues + +Reduce the input size or run with fewer workers. + +=== Future Enhancements + +* Add WASM codegen fuzzer +* Add differential testing against reference implementation +* Integrate with OSS-Fuzz for continuous fuzzing +* Add mutation testing diff --git a/tests/fuzz/FUZZING_GUIDE.md b/tests/fuzz/FUZZING_GUIDE.md deleted file mode 100644 index 1edd4cd4..00000000 --- a/tests/fuzz/FUZZING_GUIDE.md +++ /dev/null @@ -1,131 +0,0 @@ -# Fuzzing Guide for Ephapax - -This guide explains how to use the fuzz testing infrastructure for Ephapax. - -## Quick Start - -```bash -# From repository root -./tests/fuzz/run_fuzz.sh -``` - -This will: -1. Install cargo-fuzz if needed -2. Build the fuzz targets -3. Run both fuzzers for 60 seconds each - -## Fuzz Targets - -### 1. Parser Fuzzer (`parse_fuzzer`) - -**Location**: `tests/fuzz/src/parse_fuzzer/lib.rs` - -**Purpose**: Test parser robustness against arbitrary input - -**Input**: Raw bytes (converted to UTF-8) - -**Oracle**: Parser should not panic, hang, or cause memory issues - -**Example usage**: -```bash -cd tests/fuzz -cargo fuzz run parse_fuzzer -- -max_total_time=300 -``` - -### 2. Type Checker Fuzzer (`typecheck_fuzzer`) - -**Location**: `tests/fuzz/src/typecheck_fuzzer/lib.rs` - -**Purpose**: Test type checker correctness on valid ASTs - -**Input**: Raw bytes → UTF-8 → Parser → Type checker - -**Oracle**: Type checker should not panic on any valid AST - -**Example usage**: -```bash -cd tests/fuzz -cargo fuzz run typecheck_fuzzer -- -max_total_time=300 -``` - -## Corpus Management - -The seed corpus is in `tests/fuzz/corpus/`: - -```bash -# Add a new test case to parser corpus -cp my_test.eph tests/fuzz/corpus/parse_fuzzer/ - -# Add a new test case to type checker corpus -cp my_test.eph tests/fuzz/corpus/typecheck_fuzzer/ -``` - -## Advanced Usage - -### Run with specific options - -```bash -# Limit input size to 1KB -cargo fuzz run parse_fuzzer -- -max_len=1024 - -# Run with 4 workers -cargo fuzz run parse_fuzzer -- -jobs=4 - -# Run for 10 minutes -cargo fuzz run parse_fuzzer -- -max_total_time=600 -``` - -### Minimize a test case - -```bash -cargo fuzz tmin parse_fuzzer tests/fuzz/artifacts/parse_fuzzer/crash-* -``` - -### View coverage - -```bash -cargo fuzz coverage parse_fuzzer -``` - -## CI Integration - -Add to your CI workflow: - -```yaml -- name: Fuzz Test - run: | - cd tests/fuzz - cargo fuzz run parse_fuzzer -- -max_total_time=60 - cargo fuzz run typecheck_fuzzer -- -max_total_time=60 -``` - -## Safety Guidelines - -1. **Run in isolated environments**: Fuzzers can consume significant resources -2. **Set time limits**: Always use `-max_total_time` -3. **Monitor resource usage**: Fuzzing can use lots of CPU and memory -4. **Start with small inputs**: Use `-max_len=1024` initially -5. **Review crashes carefully**: Not all crashes are security issues - -## Troubleshooting - -### "No targets specified" -Make sure you're in the `tests/fuzz` directory and have run `cargo fuzz build`. - -### Build failures -Ensure all workspace dependencies are built: -```bash -cd .. -cargo build --workspace -cd tests/fuzz -``` - -### Performance issues -Reduce the input size or run with fewer workers. - -## Future Enhancements - -- Add WASM codegen fuzzer -- Add differential testing against reference implementation -- Integrate with OSS-Fuzz for continuous fuzzing -- Add mutation testing diff --git a/tests/fuzz/README.adoc b/tests/fuzz/README.adoc new file mode 100644 index 00000000..0320381c --- /dev/null +++ b/tests/fuzz/README.adoc @@ -0,0 +1,95 @@ +== Fuzz Testing for Ephapax + +This directory contains fuzz testing harnesses for Ephapax using +cargo-fuzz. + +=== Current Status + +* ✅ Property-based tests exist (proptest in +`+src/ephapax-cli/tests/property_tests.rs+`) +* ❌ No real fuzz testing implemented yet +* The `+placeholder.txt+` file was a scorecard requirement but provides +no actual fuzzing + +=== Fuzz Targets Needed + +==== 1. Parser Fuzzer + +* *Target*: `+fuzz_targets/parse_fuzzer.rs+` +* *Goal*: Find parser crashes, infinite loops, or incorrect AST +generation +* *Input*: Arbitrary byte sequences treated as UTF-8 source code +* *Oracle*: Parser should not panic or hang + +==== 2. Type Checker Fuzzer + +* *Target*: `+fuzz_targets/typecheck_fuzzer.rs+` +* *Goal*: Find type checker crashes or incorrect type assignments +* *Input*: Valid ASTs from parser (use parser fuzzer output as input) +* *Oracle*: Type checker should not panic + +==== 3. WASM Codegen Fuzzer + +* *Target*: `+fuzz_targets/codegen_fuzzer.rs+` +* *Goal*: Find code generation crashes or invalid WASM output +* *Input*: Type-checked ASTs +* *Oracle*: Generated WASM should validate and not crash wasmtime + +=== Setup Instructions + +[source,bash] +---- +# Install cargo-fuzz +cargo install cargo-fuzz + +# Initialize fuzzing in this directory +cd tests/fuzz +cargo fuzz init + +# Build fuzz targets +cargo fuzz build + +# Run parser fuzzer +cargo fuzz run parse_fuzzer +---- + +=== Corpus + +Start with a seed corpus of: - Valid Ephapax programs from `+examples/+` +- Invalid programs that currently fail gracefully - Edge cases (empty +files, very large numbers, deep nesting) + +=== Integration + +Add to root Cargo.toml: + +[source,toml] +---- +[profile.release] +debug = 1 + +[workspace] +members = [ + # ... existing members ... + "tests/fuzz", +] +---- + +=== CI Integration + +Add fuzzing to CI workflows: + +[source,yaml] +---- +- name: Fuzz Test + run: | + cd tests/fuzz + cargo fuzz run parse_fuzzer -- -max_total_time=60 +---- + +=== Safety Notes + +* Run fuzzers in isolated environments +* Set time/memory limits +* Monitor for resource exhaustion +* Use `+-max_len+` to limit input size initially diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md deleted file mode 100644 index 8f75ac3e..00000000 --- a/tests/fuzz/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Fuzz Testing for Ephapax - -This directory contains fuzz testing harnesses for Ephapax using cargo-fuzz. - -## Current Status - -- ✅ Property-based tests exist (proptest in `src/ephapax-cli/tests/property_tests.rs`) -- ❌ No real fuzz testing implemented yet -- The `placeholder.txt` file was a scorecard requirement but provides no actual fuzzing - -## Fuzz Targets Needed - -### 1. Parser Fuzzer -- **Target**: `fuzz_targets/parse_fuzzer.rs` -- **Goal**: Find parser crashes, infinite loops, or incorrect AST generation -- **Input**: Arbitrary byte sequences treated as UTF-8 source code -- **Oracle**: Parser should not panic or hang - -### 2. Type Checker Fuzzer -- **Target**: `fuzz_targets/typecheck_fuzzer.rs` -- **Goal**: Find type checker crashes or incorrect type assignments -- **Input**: Valid ASTs from parser (use parser fuzzer output as input) -- **Oracle**: Type checker should not panic - -### 3. WASM Codegen Fuzzer -- **Target**: `fuzz_targets/codegen_fuzzer.rs` -- **Goal**: Find code generation crashes or invalid WASM output -- **Input**: Type-checked ASTs -- **Oracle**: Generated WASM should validate and not crash wasmtime - -## Setup Instructions - -```bash -# Install cargo-fuzz -cargo install cargo-fuzz - -# Initialize fuzzing in this directory -cd tests/fuzz -cargo fuzz init - -# Build fuzz targets -cargo fuzz build - -# Run parser fuzzer -cargo fuzz run parse_fuzzer -``` - -## Corpus - -Start with a seed corpus of: -- Valid Ephapax programs from `examples/` -- Invalid programs that currently fail gracefully -- Edge cases (empty files, very large numbers, deep nesting) - -## Integration - -Add to root Cargo.toml: -```toml -[profile.release] -debug = 1 - -[workspace] -members = [ - # ... existing members ... - "tests/fuzz", -] -``` - -## CI Integration - -Add fuzzing to CI workflows: -```yaml -- name: Fuzz Test - run: | - cd tests/fuzz - cargo fuzz run parse_fuzzer -- -max_total_time=60 -``` - -## Safety Notes - -- Run fuzzers in isolated environments -- Set time/memory limits -- Monitor for resource exhaustion -- Use `-max_len` to limit input size initially diff --git a/tools/ephapax-doc/README.adoc b/tools/ephapax-doc/README.adoc new file mode 100644 index 00000000..ea5948f0 --- /dev/null +++ b/tools/ephapax-doc/README.adoc @@ -0,0 +1,22 @@ +== ephapax-doc + +Documentation generator for Ephapax. + +Extracts doc comments (`+///+`) from `+.eph+` files and generates +HTML/Markdown documentation, including: + +* Function signatures with qualifier annotations (● linear, ○ affine) +* Region parameters +* Type parameters +* Module hierarchy + +=== Usage + +[source,bash] +---- +ephapax doc src/ --output docs/api/ +---- + +=== Status + +Planned — depends on ephapax-syntax crate for AST parsing. diff --git a/tools/ephapax-doc/README.md b/tools/ephapax-doc/README.md deleted file mode 100644 index 48590270..00000000 --- a/tools/ephapax-doc/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# ephapax-doc - - - - -Documentation generator for Ephapax. - -Extracts doc comments (`///`) from `.eph` files and generates -HTML/Markdown documentation, including: - -- Function signatures with qualifier annotations (● linear, ○ affine) -- Region parameters -- Type parameters -- Module hierarchy - -## Usage - -```bash -ephapax doc src/ --output docs/api/ -``` - -## Status - -Planned — depends on ephapax-syntax crate for AST parsing.