Skip to content

Commit ef17164

Browse files
ABI Layer 3: no-resurrection (irreversibility) + determinism of consume (#43)
* abi: prove single-use linear token semantics (Layer 2) Add Ephapaxiser.ABI.Semantics: a faithful model of single-use token state with the headline linearity property machine-checked. - Token (Fresh|Spent) with explicit consumed-state index. - Consumable t: proposition with NO constructor for the Spent (bad) case; Uninhabited (Consumable (spent token)) discharged by impossible clause. - Consumed before after: certificate of the unique Fresh -> Spent step. - consume: advances a Fresh token to Spent given Consumable evidence. - decConsumable: total, sound + complete Dec. - certifyConsume into the shared Result codes (Ok / AlreadyConsumed), with certifyConsumeSound (no believe_me) and certifySpentRejected. - Positive controls: freshTokenConsumable, consumeFreshSeven. - Negative control: spentTokenNotConsumable : Not (Consumable spent). Builds clean (exit 0, zero warnings). A deliberately false proof that a spent token is Consumable is rejected by idris2, confirming the property is non-vacuous. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A6PSzJWpRxtzGDjUCEh7Mx * abi: add Layer-3 Invariants — no-resurrection + determinism of consume Adds Ephapaxiser.ABI.Invariants over the existing Layer-2 Semantics model (reuses Token/Consumable/Consumed/consume; no redefinition). Proves two deeper, distinct properties of the single-use transition itself: 1. NO-RESURRECTION (irreversibility): the spent post-state of any Consumed certificate is never Consumable (postStateNotConsumable). Transition soundness — every reachable post-state is dead. 2. DETERMINISM (uniqueness): the certificate is unique for a given source/ target (consumedUnique), the result identity is single-valued (consumeResultDeterministic), with a sound+complete Dec (decConsumedEq) and identity-preservation round-trip lemmas. Includes positive controls (concrete inhabited witnesses) and negative/ non-vacuity controls (no-resurrection refutation; distinct ids stay distinct). No axioms: no believe_me/idris_crash/assert_total/postulate/asserted equalities. Builds clean (exit 0, zero warnings); both deliberately-false adversarial proofs rejected. Registered last in ephapaxiser-abi.ipkg. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A6PSzJWpRxtzGDjUCEh7Mx --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c2fba07 commit ef17164

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
--
4+
||| Layer-3 deeper invariants for ephapaxiser: NO-RESURRECTION and
5+
||| DETERMINISM of the single-use consume transition.
6+
|||
7+
||| The Layer-2 flagship (`Ephapaxiser.ABI.Semantics`) proves the *guard*: a
8+
||| `Spent` token is not `Consumable`, so no second consumption is well-typed.
9+
||| This module proves two genuinely different, deeper properties about the
10+
||| transition *itself*, built over the SAME model (`Token`, `Consumable`,
11+
||| `Consumed`, `consume` are all reused, not redefined):
12+
|||
13+
||| 1. NO-RESURRECTION. The `after` token of any `Consumed` certificate is a
14+
||| `Token Spent`, and there is NO certificate whose `after` is `Fresh`.
15+
||| i.e. consumption is irreversible — a token, once spent, can never be
16+
||| shown fresh again through the transition relation. Stated as an
17+
||| `Uninhabited` instance for a "resurrection" certificate plus a positive
18+
||| identity-preservation lemma.
19+
|||
20+
||| 2. DETERMINISM. For a given fresh token, the produced certificate is
21+
||| UNIQUE: any two `Consumed (MkToken Fresh i) _` certificates are equal,
22+
||| and the spent token `consume` produces is uniquely determined (same
23+
||| identity, propositionally). This is a uniqueness-of-result theorem,
24+
||| not a guard — it says the single legal transition is a *function*, not
25+
||| merely *at most once permitted*.
26+
|||
27+
||| Together these raise the ABI to "Layer 3": beyond the domain invariant we
28+
||| prove the transition is irreversible and deterministic. No axioms: no
29+
||| `believe_me`, `idris_crash`, `assert_total`, `postulate`, or asserted
30+
||| equalities anywhere.
31+
32+
module Ephapaxiser.ABI.Invariants
33+
34+
import Ephapaxiser.ABI.Types
35+
import Ephapaxiser.ABI.Semantics
36+
import Decidable.Equality
37+
38+
%default total
39+
40+
--------------------------------------------------------------------------------
41+
-- Identity preservation through the transition (a round-trip lemma).
42+
--------------------------------------------------------------------------------
43+
44+
||| The identity tag carried by a `Consumed` certificate's source equals that of
45+
||| its target. This is the round-trip content: `consume` neither invents nor
46+
||| forgets identity. Proved by matching the unique certificate constructor.
47+
public export
48+
consumedPreservesId : {0 b : Token Fresh} -> {0 a : Token Spent} ->
49+
Consumed b a -> tokenId b = tokenId a
50+
consumedPreservesId (MkConsumed i) = Refl
51+
52+
||| The spent token produced by `consume` has the same identity as the fresh
53+
||| token it consumed. Direct corollary stated over the operation itself.
54+
public export
55+
consumePreservesId : (i : Nat) ->
56+
tokenId (fst (consume (MkToken {st = Fresh} i) FreshConsumable)) = i
57+
consumePreservesId i = Refl
58+
59+
--------------------------------------------------------------------------------
60+
-- (1) NO-RESURRECTION: the transition is irreversible.
61+
--------------------------------------------------------------------------------
62+
63+
||| A "resurrection" claim is the proposition that the SPENT result of the
64+
||| transition can itself be reclassified as a `Fresh`, consumable token — i.e.
65+
||| that the token has come back to life. We phrase it as: the spent result is
66+
||| `Consumable`. Since `Consumable` has NO constructor for a `Spent` token
67+
||| (Layer-2), this proposition is uninhabited — that is exactly irreversibility.
68+
public export
69+
Resurrection : (after : Token Spent) -> Type
70+
Resurrection after = Consumable after
71+
72+
||| NO-RESURRECTION (theorem): the spent result of ANY transition certificate is
73+
||| not resurrectable. Given `Consumed b a`, the target `a` is a `Token Spent`,
74+
||| and a `Token Spent` is never `Consumable` (Layer-2 `Uninhabited` instance).
75+
||| This is a transition-soundness theorem: every reachable post-state is dead.
76+
||| Proved without axioms — `a`'s state index is forced to `Spent` by the
77+
||| certificate's type, so `absurd` discharges any `Consumable a`.
78+
public export
79+
postStateNotConsumable : {0 b : Token Fresh} -> (0 a : Token Spent) ->
80+
Consumed b a -> Not (Resurrection a)
81+
postStateNotConsumable (MkToken i) (MkConsumed i) = absurd
82+
83+
||| NO-RESURRECTION (operational corollary): for a concrete identity, a spent
84+
||| token can never be re-presented as a fresh, consumable token. Reuses the
85+
||| Layer-2 `Uninhabited (Consumable (MkToken {st = Spent} i))` over the result
86+
||| of `consume`: the produced token is `Spent`, hence not `Consumable`.
87+
public export
88+
consumedResultNotConsumable : (i : Nat) ->
89+
Not (Consumable (DPair.fst (consume (MkToken {st = Fresh} i) FreshConsumable)))
90+
consumedResultNotConsumable i = absurd
91+
92+
--------------------------------------------------------------------------------
93+
-- (2) DETERMINISM: the certificate / result is unique for a given fresh token.
94+
--------------------------------------------------------------------------------
95+
96+
||| DETERMINISM (certificate uniqueness): any two transition certificates with
97+
||| the SAME fresh source and the SAME spent target are equal. Because
98+
||| `MkConsumed` is the unique constructor and is fully determined by the shared
99+
||| identity index, the two certificates are literally the same value.
100+
public export
101+
consumedUnique : {0 b : Token Fresh} -> {0 a : Token Spent} ->
102+
(p, q : Consumed b a) -> p = q
103+
consumedUnique (MkConsumed i) (MkConsumed i) = Refl
104+
105+
||| DETERMINISM (result identity is a function): if two certificates share a
106+
||| fresh source `b`, their spent targets carry the SAME identity. So the
107+
||| transition cannot map one fresh token to two differently-identified spent
108+
||| tokens — it is single-valued in identity.
109+
|||
110+
||| We extract identities and chain the round-trip lemma in both directions:
111+
||| `tokenId a1 = tokenId b = tokenId a2`.
112+
public export
113+
consumeResultDeterministic : {0 b : Token Fresh} -> {0 a1, a2 : Token Spent} ->
114+
Consumed b a1 -> Consumed b a2 ->
115+
tokenId a1 = tokenId a2
116+
consumeResultDeterministic c1 c2 =
117+
trans (sym (consumedPreservesId c1)) (consumedPreservesId c2)
118+
119+
||| DETERMINISM (operation level): running `consume` twice on the SAME fresh
120+
||| token yields spent tokens of the SAME identity. Definitional here, but
121+
||| stating it pins the determinism of the actual operation, not just the
122+
||| relation.
123+
public export
124+
consumeFunction : (i : Nat) ->
125+
tokenId (fst (consume (MkToken {st = Fresh} i) FreshConsumable))
126+
= tokenId (fst (consume (MkToken {st = Fresh} i) FreshConsumable))
127+
consumeFunction i = Refl
128+
129+
--------------------------------------------------------------------------------
130+
-- Decision procedure (natural here): decide whether two certificates over the
131+
-- same source/target indices are equal. Sound + complete: there is exactly one
132+
-- such certificate, so the answer is always Yes, and the witness is genuine.
133+
--------------------------------------------------------------------------------
134+
135+
||| Decide equality of two `Consumed` certificates with matched indices. Sound
136+
||| (the `Yes` carries a real equality proof from `consumedUnique`) and complete
137+
||| (it never returns `No`, because by uniqueness they are always equal — there
138+
||| is no inhabited `Not (p = q)` to return). This is the decidable face of the
139+
||| determinism theorem.
140+
public export
141+
decConsumedEq : {0 b : Token Fresh} -> {0 a : Token Spent} ->
142+
(p, q : Consumed b a) -> Dec (p = q)
143+
decConsumedEq p q = Yes (consumedUnique p q)
144+
145+
--------------------------------------------------------------------------------
146+
-- POSITIVE controls (inhabited witnesses / concrete instances).
147+
--------------------------------------------------------------------------------
148+
149+
||| POSITIVE CONTROL: a concrete certificate exists, and the round-trip lemma
150+
||| computes the preserved identity to the literal `5`.
151+
public export
152+
consumedFiveId : Invariants.consumedPreservesId (MkConsumed 5)
153+
= the (5 = 5) Refl
154+
consumedFiveId = Refl
155+
156+
||| POSITIVE CONTROL: determinism delivers the SAME concrete certificate when
157+
||| asked twice for the id-9 transition.
158+
public export
159+
sameCertNine : Invariants.consumedUnique (MkConsumed 9) (MkConsumed 9) = Refl
160+
sameCertNine = Refl
161+
162+
||| POSITIVE CONTROL: the decision procedure answers `Yes` for two equal
163+
||| concrete certificates, with the genuine uniqueness proof inside.
164+
public export
165+
decYesNine : Invariants.decConsumedEq (MkConsumed 9) (MkConsumed 9)
166+
= Yes Refl
167+
decYesNine = Refl
168+
169+
--------------------------------------------------------------------------------
170+
-- NEGATIVE / non-vacuity controls (machine-checked).
171+
--------------------------------------------------------------------------------
172+
173+
||| NEGATIVE CONTROL (no-resurrection): the spent result of consuming fresh-id-3
174+
||| cannot be resurrected — it is not `Consumable`. Discharged via the
175+
||| no-resurrection theorem applied to the concrete id-3 certificate.
176+
public export
177+
noResurrectionThree : Not (Resurrection (MkToken {st = Spent} 3))
178+
noResurrectionThree =
179+
postStateNotConsumable (MkToken 3) (MkConsumed 3)
180+
181+
||| NEGATIVE CONTROL (irreversibility, operational): the spent token produced by
182+
||| consuming fresh-id-3 is NOT consumable — it cannot loop back to a usable
183+
||| state. This is non-vacuous: it exercises a real `consume` result.
184+
public export
185+
consumeThreeResultDead : Not (Consumable (DPair.fst (consume (MkToken {st = Fresh} 3) FreshConsumable)))
186+
consumeThreeResultDead = absurd
187+
188+
||| NON-VACUITY CONTROL: distinct fresh sources produce distinct spent
189+
||| identities — determinism is single-valued, NOT collapse-everything. Here
190+
||| consuming id-1 and id-2 give targets whose ids differ, refuting `1 = 2`.
191+
||| (If determinism were vacuous/degenerate this would not hold.)
192+
public export
193+
distinctIdsDistinctResults :
194+
Not (tokenId (fst (consume (MkToken {st = Fresh} 1) FreshConsumable))
195+
= tokenId (fst (consume (MkToken {st = Fresh} 2) FreshConsumable)))
196+
distinctIdsDistinctResults Refl impossible

src/interface/abi/ephapaxiser-abi.ipkg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ modules = Ephapaxiser.ABI.Types
1010
, Ephapaxiser.ABI.Foreign
1111
, Ephapaxiser.ABI.Proofs
1212
, Ephapaxiser.ABI.Semantics
13+
, Ephapaxiser.ABI.Invariants

0 commit comments

Comments
 (0)