|
| 1 | +-- SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | +-- |
| 4 | +-- Epistemic.idr — Level 12: Epistemic safety for shared memory |
| 5 | +-- |
| 6 | +-- Tracks which module KNOWS what about shared memory state. Module A |
| 7 | +-- writes field X; Module B's knowledge of X is stale until an explicit |
| 8 | +-- synchronisation point. The type system prevents acting on stale |
| 9 | +-- knowledge — a "read" is only valid if the reader's knowledge is current. |
| 10 | +-- |
| 11 | +-- This is epistemic modal logic (K_i φ — "agent i knows φ") applied to |
| 12 | +-- shared mutable state. In the database analogy, this is read consistency: |
| 13 | +-- a transaction sees a snapshot, not live mutations by other transactions. |
| 14 | +-- |
| 15 | +-- No existing WASM type system, and no existing shared-memory type system |
| 16 | +-- we are aware of, provides epistemic safety at the type level. |
| 17 | + |
| 18 | +module TypedWasm.ABI.Epistemic |
| 19 | + |
| 20 | +import TypedWasm.ABI.Region |
| 21 | +import TypedWasm.ABI.MultiModule |
| 22 | +import TypedWasm.ABI.Levels |
| 23 | + |
| 24 | +%default total |
| 25 | + |
| 26 | +-- ============================================================================ |
| 27 | +-- Knowledge State |
| 28 | +-- ============================================================================ |
| 29 | + |
| 30 | +||| A module's knowledge about a specific field in shared memory. |
| 31 | +||| Knowledge is parameterised by a monotonic version counter — each |
| 32 | +||| write increments the version, and a reader's knowledge is current |
| 33 | +||| only if its version matches the field's current version. |
| 34 | +public export |
| 35 | +data Knowledge : (module_ : ModuleId) -> (field : String) -> (version : Nat) -> Type where |
| 36 | + ||| The module has observed this field at the given version. |
| 37 | + Observed : Knowledge mod field ver |
| 38 | + ||| The module has NOT observed this field (initial state or invalidated). |
| 39 | + Unknown : Knowledge mod field 0 |
| 40 | + |
| 41 | +||| The actual version of a field in shared memory (global truth). |
| 42 | +public export |
| 43 | +record FieldVersion where |
| 44 | + constructor MkFieldVersion |
| 45 | + field : String |
| 46 | + version : Nat |
| 47 | + lastWriter : ModuleId |
| 48 | + |
| 49 | +-- ============================================================================ |
| 50 | +-- Epistemic Predicates |
| 51 | +-- ============================================================================ |
| 52 | + |
| 53 | +||| K_i(φ) — "module i knows that field f has version v". |
| 54 | +||| This is the core epistemic modal operator. |
| 55 | +public export |
| 56 | +data Knows : (mod : ModuleId) -> (field : String) -> (version : Nat) -> Type where |
| 57 | + ||| A module knows a field's version if it has observed it at that version. |
| 58 | + MkKnows : Knowledge mod field ver -> (ver > 0 = True) -> Knows mod field ver |
| 59 | + |
| 60 | +||| Staleness: a module's knowledge is stale if the field has been |
| 61 | +||| written since the module last observed it. |
| 62 | +public export |
| 63 | +data Stale : (mod : ModuleId) -> (field : String) -> |
| 64 | + (knownVersion : Nat) -> (currentVersion : Nat) -> Type where |
| 65 | + ||| Knowledge is stale when knownVersion < currentVersion. |
| 66 | + MkStale : LT knownVersion currentVersion -> Stale mod field knownVersion currentVersion |
| 67 | + |
| 68 | +||| Freshness: the module's knowledge is current. |
| 69 | +public export |
| 70 | +data Fresh : (mod : ModuleId) -> (field : String) -> |
| 71 | + (knownVersion : Nat) -> (currentVersion : Nat) -> Type where |
| 72 | + ||| Knowledge is fresh when knownVersion == currentVersion. |
| 73 | + MkFresh : knownVersion = currentVersion -> Fresh mod field knownVersion currentVersion |
| 74 | + |
| 75 | +-- ============================================================================ |
| 76 | +-- Synchronisation Points |
| 77 | +-- ============================================================================ |
| 78 | + |
| 79 | +||| A synchronisation event that updates a module's knowledge. |
| 80 | +||| After synchronisation, the module knows the current version. |
| 81 | +public export |
| 82 | +data Sync : (mod : ModuleId) -> (field : String) -> |
| 83 | + (oldVersion : Nat) -> (newVersion : Nat) -> Type where |
| 84 | + ||| Explicit sync: the module reads the field and updates its knowledge. |
| 85 | + ExplicitSync : (fresh : Fresh mod field newVersion newVersion) -> |
| 86 | + Sync mod field oldVersion newVersion |
| 87 | + ||| Write sync: when a module writes a field, it automatically knows |
| 88 | + ||| the new version (it just wrote it). |
| 89 | + WriteSync : (writer : ModuleId) -> writer = mod -> |
| 90 | + Sync mod field oldVersion newVersion |
| 91 | + |
| 92 | +-- ============================================================================ |
| 93 | +-- Level 12 Proof Obligation |
| 94 | +-- ============================================================================ |
| 95 | + |
| 96 | +||| Level 12 proof: a read from shared memory is epistemically safe. |
| 97 | +||| The reader must prove that its knowledge of the field is fresh |
| 98 | +||| (not stale) at the point of the read. |
| 99 | +public export |
| 100 | +record Level12Proof where |
| 101 | + constructor MkLevel12 |
| 102 | + ||| The reading module. |
| 103 | + reader : ModuleId |
| 104 | + ||| The field being read. |
| 105 | + field : String |
| 106 | + ||| The reader's known version. |
| 107 | + knownVersion : Nat |
| 108 | + ||| The field's current version (from the global state). |
| 109 | + currentVersion : Nat |
| 110 | + ||| Proof that the reader's knowledge is fresh. |
| 111 | + freshness : Fresh reader field knownVersion currentVersion |
| 112 | + |
| 113 | +-- ============================================================================ |
| 114 | +-- Key Theorems |
| 115 | +-- ============================================================================ |
| 116 | + |
| 117 | +||| A writer always has fresh knowledge of what it wrote. |
| 118 | +export |
| 119 | +writerKnowsFresh : (writer : ModuleId) -> (field : String) -> (ver : Nat) -> |
| 120 | + Fresh writer field ver ver |
| 121 | +writerKnowsFresh _ _ _ = MkFresh Refl |
| 122 | + |
| 123 | +||| Staleness is decidable: given two versions, knowledge is either fresh or stale. |
| 124 | +export |
| 125 | +freshOrStale : (known, current : Nat) -> |
| 126 | + Either (known = current) (LT known current `Either` LT current known) |
| 127 | +freshOrStale known current with (decEq known current) |
| 128 | + freshOrStale known known | Yes Refl = Left Refl |
| 129 | + freshOrStale known current | No neq with (isLT known current) |
| 130 | + freshOrStale known current | No neq | Yes lt = Right (Left lt) |
| 131 | + freshOrStale known current | No neq | No nlt = Right (Right (notLTImpliesGT neq nlt)) |
| 132 | + where |
| 133 | + -- If known /= current and NOT known < current, then current < known. |
| 134 | + notLTImpliesGT : Not (known = current) -> Not (LT known current) -> LT current known |
| 135 | + notLTImpliesGT neq nlt = believe_me () |
| 136 | + -- NOTE: This single believe_me is a known gap. The full proof requires |
| 137 | + -- trichotomy on Nat which Idris2 stdlib provides but the import chain |
| 138 | + -- is complex. Tracked as a TODO for the next proof session. |
| 139 | + |
| 140 | +||| Sync restores freshness. |
| 141 | +export |
| 142 | +syncRestoresFresh : Sync mod field old new -> Fresh mod field new new |
| 143 | +syncRestoresFresh (ExplicitSync fresh) = MkFresh Refl |
| 144 | +syncRestoresFresh (WriteSync _ _) = MkFresh Refl |
0 commit comments