Skip to content

Commit f60bd53

Browse files
proof(formal): general linear-coverage theorem — splitLinearCoverage (#85)
## Proof-debt P0 — ephapax (epic standards#124, arm standards#134) ### What `src/formal/Ephapax/Formal/Qualifier.idr` documented (on `Split`) that **every linear binding, at any position in the context, appears in exactly one sub-context**, but only `nonDiminishment` was proved — and that covers solely a linear binding at the **head** of the context. This adds **`splitLinearCoverage`**, the general theorem: ```idris splitLinearCoverage : (s : Split full left right) -> (b : Binding) -> b.bindQual = Linear -> Elem b full -> Either (Elem b left) (Elem b right) ``` Pure structural induction over the `Split` derivation. Affine-head / `SplitAffDrop` cases are discharged via a `private` `Uninhabited (Affine = Linear)` (the head cannot be the linear `b`); `SplitNil` via `absurd` on the empty-list `Elem`. ### Oracle `idris2 0.8.0` build of `src/formal/ephapax-formal.ipkg` — **3/3 modules green** before and after. No `believe_me` / `assert_*` / holes; `%default total` in force, so the new clauses are total + covering. ### Scope (deliberately bounded — not over-claimed) Proves **coverage** (linear bindings are never dropped — the non-diminishment direction generalised to all positions). It does **not** assert exclusivity ("exactly one" under value-equal duplicate bindings) — a distinct, stronger property left as future work and explicitly NOT claimed in the docstring. ### Out of scope (still OWED on standards#134) - 14 `idris2/src/*.idr` lacking SPDX headers — owner-gated, not auto-edited. - Stale docs (ROADMAP "92%" vs actual; Coq `preservation` is `Qed` not Admitted) — tracked separately. - No SPARK seam exists in this repo. Refs hyperpolymath/standards#134 Refs hyperpolymath/standards#124 (Refs, not Closes — human-gated.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d2541f9 commit f60bd53

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

src/formal/Ephapax/Formal/Qualifier.idr

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
module Ephapax.Formal.Qualifier
1616

1717
import Data.List
18+
import Data.List.Elem
1819

1920
%default total
2021

@@ -184,3 +185,63 @@ public export
184185
nonDiminishment : (s : Split (MkBinding n Linear t :: rest) left right) -> LinearPreserved s
185186
nonDiminishment (SplitLinL _) = PreservedLeft
186187
nonDiminishment (SplitLinR _) = PreservedRight
188+
189+
--------------------------------------------------------------------------------
190+
-- Linear Coverage (general form of Theorem 5.1)
191+
--------------------------------------------------------------------------------
192+
193+
||| `nonDiminishment` only covers a linear binding at the HEAD of the context.
194+
||| The docstring on `Split` (above) makes the stronger claim that *every*
195+
||| linear binding — at any position in `full` — appears in (at least) one of
196+
||| the two sub-contexts. That general statement was previously unproven.
197+
|||
198+
||| `splitLinearCoverage` discharges it: for any split and any linear binding
199+
||| that is a member of the full context, that binding is a member of the left
200+
||| or of the right sub-context (i.e. it is never dropped). The proof is pure
201+
||| structural induction over the `Split` derivation.
202+
|||
203+
||| Scope: this proves COVERAGE (linear bindings are not dropped — the "at
204+
||| least one" / non-diminishment direction). It does not assert exclusivity
205+
||| ("exactly one"): with value-equal duplicate bindings that is a distinct,
206+
||| stronger property and is left as future work; it is intentionally NOT
207+
||| claimed here.
208+
private
209+
Uninhabited (Affine = Linear) where
210+
uninhabited Refl impossible
211+
212+
public export
213+
splitLinearCoverage : (s : Split full left right)
214+
-> (b : Binding)
215+
-> b.bindQual = Linear
216+
-> Elem b full
217+
-> Either (Elem b left) (Elem b right)
218+
splitLinearCoverage SplitNil _ _ el = absurd el
219+
-- Linear head goes left
220+
splitLinearCoverage (SplitLinL _) _ _ Here = Left Here
221+
splitLinearCoverage (SplitLinL s) b prf (There e) =
222+
case splitLinearCoverage s b prf e of
223+
Left el => Left (There el)
224+
Right er => Right er
225+
-- Linear head goes right
226+
splitLinearCoverage (SplitLinR _) _ _ Here = Right Here
227+
splitLinearCoverage (SplitLinR s) b prf (There e) =
228+
case splitLinearCoverage s b prf e of
229+
Left el => Left el
230+
Right er => Right (There er)
231+
-- Affine head, kept left: the head cannot be our linear `b`
232+
splitLinearCoverage (SplitAffL _) _ prf Here = absurd prf
233+
splitLinearCoverage (SplitAffL s) b prf (There e) =
234+
case splitLinearCoverage s b prf e of
235+
Left el => Left (There el)
236+
Right er => Right er
237+
-- Affine head, kept right
238+
splitLinearCoverage (SplitAffR _) _ prf Here = absurd prf
239+
splitLinearCoverage (SplitAffR s) b prf (There e) =
240+
case splitLinearCoverage s b prf e of
241+
Left el => Left el
242+
Right er => Right (There er)
243+
-- Affine head, dropped (weakening): the head cannot be our linear `b`,
244+
-- and the sub-contexts are unchanged by the drop.
245+
splitLinearCoverage (SplitAffDrop _) _ prf Here = absurd prf
246+
splitLinearCoverage (SplitAffDrop s) b prf (There e) =
247+
splitLinearCoverage s b prf e

0 commit comments

Comments
 (0)