|
| 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 |
0 commit comments