|
| 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 | += local-coord-mcp — proof schedule |
| 5 | +:author: Jonathan D.A. Jewell |
| 6 | +:date: 2026-04-20 |
| 7 | +:toc: macro |
| 8 | +:version: 1 |
| 9 | + |
| 10 | +Companion to `SafeLocalCoord.idr` (already-proved invariants) and |
| 11 | +`Protocol.idr` (message-passing algebra). This document enumerates every |
| 12 | +formal-verification obligation the cartridge carries, ranks them by |
| 13 | +priority, names the prover, and sequences the work. |
| 14 | + |
| 15 | +Scope is *Phase 1* (local, hermetic). Phase 2+ obligations (choreographic / |
| 16 | +epistemic / echo / tropical) are listed but deferred per |
| 17 | +`Desktop/COORD-MCP-DESIGN-LOG.md` Appendices H-J. |
| 18 | + |
| 19 | +toc::[] |
| 20 | + |
| 21 | +== Summary |
| 22 | + |
| 23 | +[cols="1,4,1,1,2",options="header"] |
| 24 | +|=== |
| 25 | +| # | Obligation | Prior | Status | Prover |
| 26 | +| P-00 | Loopback-only bind | — | ✅ done | Idris2 |
| 27 | +| P-01 | Port in non-privileged range | — | ✅ done | Idris2 |
| 28 | +| P-02 | Federation policy ≡ LocalOnly uninhabited elsewhere | — | ✅ done | Idris2 |
| 29 | +| P-03 | Session-token non-empty on issue | — | ✅ done (type-level) | Idris2 |
| 30 | +| P-04 | Record format invariants (magic ∧ version ∧ CRC) | P-00…P-03 | P0 | Idris2 |
| 31 | +| P-05 | CRC truncation tolerance (prefix-closed replay) | P-04 | P0 | Idris2 |
| 32 | +| P-06 | Replay-equivalence (persist-then-replay ≡ direct execute) | P-04, P-05 | P0 | Idris2 |
| 33 | +| P-07 | Quarantine state machine (determinism + monotonicity) | P-06 | P0 | Idris2 |
| 34 | +| P-08 | Ring-buffer FIFO correctness under replay | P-06 | P1 | Idris2 |
| 35 | +| P-09 | Persistence monotonicity (commits cannot un-happen) | P-05, P-06 | P1 | Idris2 |
| 36 | +| P-10 | Supervisor privilege gating (env-var secret necessary) | P-03 | P1 | Idris2 |
| 37 | +| P-11 | Tier-4 forbidden-list schema-level reject for `supervised` | P-10 | P1 | Nickel + Idris2 |
| 38 | +| P-12 | Nickel-contract soundness wrt risk-ladder spec | P-11 | P2 | Lean4 ‖ Idris2 |
| 39 | +| P-13 | Zig ↔ Idris2 ABI correspondence (hardcoded `127.0.0.1` realises `IsIPv4Loop`) | P-00 | P2 | Idris2 + SPARK-style assertions |
| 40 | +| P-14 | Token unforgeability (probabilistic; 128-bit CSPRNG) | P-03 | documented | prose argument |
| 41 | +| P-15 | Choreographic types for supervisor/attestation flow | P-06, P-07 | Phase 2 (deferred) | Idris2 session types |
| 42 | +| P-16 | Epistemic types for context-knowledge-witness | P-15 | Phase 2 (deferred) | Idris2 + A2ML tags |
| 43 | +| P-17 | Echo-type formalisation of audit + summary + hash chain | P-15, P-16 | Phase 3 (deferred) | Agda (echo-types repo) |
| 44 | +| P-18 | Tropical-semiring model of TTL + trust | P-17 | Phase 3 (deferred) | Agda (EchoTropical) |
| 45 | +| P-19 | Cross-site primacy ceremony | P-15…P-18 | Phase 4 (deferred) | Idris2 + Agda |
| 46 | +|=== |
| 47 | + |
| 48 | +Stage priorities: |
| 49 | + |
| 50 | +P0 (**must land before Task #8 e2e sign-off**) → P-04, P-05, P-06, P-07. |
| 51 | + |
| 52 | +P1 (**should land before track-record work, Task #13**) → P-08, P-09, P-10, P-11. |
| 53 | + |
| 54 | +P2 (**nice-to-have; unblocks Task #17 Deno shim formal verification**) → P-12, P-13. |
| 55 | + |
| 56 | +== Already proved — carry forward |
| 57 | + |
| 58 | +=== P-00 Loopback-only bind |
| 59 | + |
| 60 | +`SafeLocalCoord.loopbackToString` is total, and `IsLoopback addr` has |
| 61 | +exactly two inhabitants (`IsIPv4Loop`, `IsIPv6Loop`). `BindConfig` |
| 62 | +requires a `LoopbackAddr` value, and the only constructors map to |
| 63 | +"127.0.0.1" or "::1". `wildcardNotLoopback : IsLoopback "0.0.0.0" -> Void` |
| 64 | +closes the negative case. |
| 65 | + |
| 66 | +=== P-01 Port range |
| 67 | + |
| 68 | +`ValidPort p` requires `LTE 1024 p` and `LTE p 65535` implicit args. |
| 69 | +`coordPortValid : ValidPort 7745` is the specific instance used. |
| 70 | + |
| 71 | +=== P-02 Federation opt-out |
| 72 | + |
| 73 | +`FederationPolicy` has one constructor (`LocalOnly`). `IsFederated` is |
| 74 | +defined with no constructor — uninhabited. `localOnlyNotFederated` |
| 75 | +closes the case. |
| 76 | + |
| 77 | +=== P-03 Session token non-emptiness |
| 78 | + |
| 79 | +`SessionToken`'s constructor demands `NonEmpty (unpack tok)`. |
| 80 | + |
| 81 | +These are the spine SafeLocalCoord.idr already enforces. Every new |
| 82 | +obligation below composes with them. |
| 83 | + |
| 84 | +== New obligations (this task + Task #7 durability layer) |
| 85 | + |
| 86 | +=== P-04 Record format invariants [P0] |
| 87 | + |
| 88 | +*Claim.* For every record `r` successfully decoded by the replay |
| 89 | +iterator, `r.magic = MAGIC ∧ r.format_version = 1 ∧ crc32(header(r) ‖ |
| 90 | +payload(r)) = r.crc`. |
| 91 | + |
| 92 | +*Artefact.* `abi/LocalCoord/Durability.idr` defines a record type |
| 93 | +`DurableRecord` whose constructor requires all three witnesses as |
| 94 | +implicit args. `decodeOne : BitStream -> Maybe DurableRecord` total. |
| 95 | + |
| 96 | +*Dependencies.* Idris2 library for CRC32 (or a cheap algebraic stand-in |
| 97 | +for the proof; the CRC need only be opaque + extensional). |
| 98 | + |
| 99 | +*Acceptance.* `decodeOne` is shown to produce only well-formed records; |
| 100 | +any corruption yields `Nothing`. |
| 101 | + |
| 102 | +=== P-05 CRC truncation tolerance [P0] |
| 103 | + |
| 104 | +*Claim.* For any log `L` with a valid prefix `L_good` followed by a |
| 105 | +corrupt tail `L_bad` (bad magic, wrong version, or failed CRC), replay |
| 106 | +accepts exactly the events in `L_good` and emits nothing for `L_bad`. |
| 107 | + |
| 108 | +*Artefact.* `Durability.idr`: |
| 109 | +`replay : BitStream -> List DurableRecord` total + a corollary |
| 110 | +`replayPrefixClosed : (L_good, L_bad : BitStream) -> (allValid L_good) -> |
| 111 | +(¬ allValid L_bad) -> replay (L_good ‖ L_bad) = replay L_good`. |
| 112 | + |
| 113 | +*Dependencies.* P-04. |
| 114 | + |
| 115 | +*Acceptance.* Matches `test "replay stops at CRC corruption"` in |
| 116 | +`coord_durability.zig` — property-test lines up with theorem statement. |
| 117 | + |
| 118 | +=== P-06 Replay-equivalence [P0 — keystone theorem] |
| 119 | + |
| 120 | +*Claim.* Let `σ : State` be the coord in-memory state. Let `step : |
| 121 | +State -> Mutation -> State` be the direct mutation step. Let `logStep : |
| 122 | +LogBuffer -> Mutation -> LogBuffer` be the append side. Let `replayInto |
| 123 | +: LogBuffer -> State -> State` be the dispatcher. Then for any |
| 124 | +mutation sequence `ms : List Mutation` and initial `σ0`, |
| 125 | + |
| 126 | + foldl step σ0 ms ≡ replayInto (foldl logStep [] ms) σ0 |
| 127 | + |
| 128 | +*Interpretation.* Persist-then-replay is observationally equivalent to |
| 129 | +direct execution. The durability layer never changes semantics. |
| 130 | + |
| 131 | +*Artefact.* `abi/LocalCoord/Durability.idr` bundles `step`, `logStep`, |
| 132 | +`replayInto` with the equivalence lemma `replayEquivalent`. |
| 133 | + |
| 134 | +*Dependencies.* P-04, P-05. Requires encoding coord `State` (peers, |
| 135 | +claims, quarantine, next_request_id) in Idris2 — 80% overlap with |
| 136 | +`Protocol.idr` state. |
| 137 | + |
| 138 | +*Acceptance.* Unit-tested empirically already by |
| 139 | +`test "restart replay restores peer, claim, inbox, quarantine"`; proof |
| 140 | +closes the case for arbitrary mutation sequences. |
| 141 | + |
| 142 | +*Subtlety.* The replay dispatcher uses "first free slot" for quarantine |
| 143 | +reconstruction. The theorem must state slot assignments need not match |
| 144 | +the original run — only the *active-entry-set* (modulo slot identity) |
| 145 | +must. This is the right abstraction since callers address entries by |
| 146 | +`request_id`, not by slot index. |
| 147 | + |
| 148 | +=== P-07 Quarantine state machine [P0] |
| 149 | + |
| 150 | +*Claim.* For every `request_id r`, the log contains at most one terminal |
| 151 | +event among `{QUAR_APPROVE r, QUAR_REJECT r}` after `QUAR_ADD r`. After |
| 152 | +a terminal event, no further events reference `r`. |
| 153 | + |
| 154 | +*State chart.* |
| 155 | +[cols="1,1,1,1",options="header"] |
| 156 | +|=== |
| 157 | +| State | QUAR_ADD | QUAR_APPROVE | QUAR_REJECT |
| 158 | +| initial | → pending (ok) | error | error |
| 159 | +| pending | error | → approved (ok) | → rejected (ok) |
| 160 | +| approved | error | error | error |
| 161 | +| rejected | error | error | error |
| 162 | +|=== |
| 163 | + |
| 164 | +*Artefact.* `abi/LocalCoord/Quarantine.idr` — state-machine witness with |
| 165 | +`QuarState` enum, `validTransition` predicate, and a soundness lemma |
| 166 | +that the event stream respects it. |
| 167 | + |
| 168 | +*Dependencies.* P-06. |
| 169 | + |
| 170 | +*Acceptance.* Existing `coord_approve` / `coord_reject` are single-shot |
| 171 | +(they deactivate the entry); the code meets the spec already — proof |
| 172 | +just makes it type-level. |
| 173 | + |
| 174 | +=== P-08 Ring-buffer FIFO correctness [P1] |
| 175 | + |
| 176 | +*Claim.* Under a schedule of `INBOX_PUSH(peer, msg_i)` and |
| 177 | +`INBOX_POP(peer)` events with the invariant `count ≤ MAX_MESSAGES`, the |
| 178 | +sequence of messages returned by consecutive `coord_receive` calls is |
| 179 | +the sub-sequence of pushed messages whose indices exceed the number of |
| 180 | +prior pops. (FIFO.) |
| 181 | + |
| 182 | +*Artefact.* `abi/LocalCoord/RingBuffer.idr` — with `Vect n Msg` indexed |
| 183 | +by queue depth + push/pop operations + `popOrderFifo` lemma. |
| 184 | + |
| 185 | +*Dependencies.* P-06 (to lift single-peer reasoning to the replay-aware |
| 186 | +form). |
| 187 | + |
| 188 | +*Acceptance.* Corresponds to the `"send and receive direct message"` |
| 189 | +and `"broadcast message"` Zig tests. |
| 190 | + |
| 191 | +=== P-09 Persistence monotonicity [P1] |
| 192 | + |
| 193 | +*Claim.* Let `L_t` be the log at wall-clock time `t`. For `t1 < t2` |
| 194 | +without intervening truncation or CRC-destroying corruption, `replay |
| 195 | +L_t1 ⊆ replay L_t2` (prefix-wise). |
| 196 | + |
| 197 | +*Artefact.* `Durability.idr` lemma `replayMonotone`. |
| 198 | + |
| 199 | +*Dependencies.* P-05. |
| 200 | + |
| 201 | +*Acceptance.* Matches the in-code guarantee that `logPeerAdd` et al. |
| 202 | +only *append*, never overwrite. |
| 203 | + |
| 204 | +=== P-10 Supervisor privilege gating [P1] |
| 205 | + |
| 206 | +*Claim.* There is no execution in which `peers[i].role = supervisor` |
| 207 | +without one of: |
| 208 | + |
| 209 | +. a prior call to `coord_promote_to_supervisor(tok_i, secret, ...)` with |
| 210 | + `secret ≡ getenv(BOJ_SUPERVISOR_TOKEN)` (constant-time match), *or* |
| 211 | +. a test-only direct mutation (explicitly out of scope — the proof |
| 212 | + applies to the public API only). |
| 213 | + |
| 214 | +*Artefact.* `abi/LocalCoord/Role.idr` — `Role` type with phantom |
| 215 | +witness `PromotedVia`; public API functions require a `PromotedVia` |
| 216 | +argument to produce a `supervisor`-tagged peer. |
| 217 | + |
| 218 | +*Dependencies.* P-03. |
| 219 | + |
| 220 | +*Acceptance.* Corresponds to the run-time rejection paths already |
| 221 | +wired (return codes -3 / -4 / promote-requires-env-var). |
| 222 | + |
| 223 | +=== P-11 Tier-4 forbidden list [P1] |
| 224 | + |
| 225 | +*Claim.* For any envelope `e` with `sender_role = supervised` and |
| 226 | +`op_kind` matching a Tier-4 forbidden pattern (force-push, branch |
| 227 | +delete, public-repo create, license change, always-private-repo touch), |
| 228 | +`validate e` rejects. |
| 229 | + |
| 230 | +*Artefact.* New `forbiddenT4Patterns` predicate in |
| 231 | +`coord-messages-contracts.ncl` + Idris2 wrapper proving |
| 232 | +exhaustiveness over the current taxonomy (schema-driven). |
| 233 | + |
| 234 | +*Dependencies.* P-10. |
| 235 | + |
| 236 | +*Acceptance.* Gates the envelope before it reaches the FFI. |
| 237 | + |
| 238 | +=== P-12 Nickel-contract soundness wrt risk-ladder spec [P2] |
| 239 | + |
| 240 | +*Claim.* The four dependent contracts (TierContextGate, |
| 241 | +TierAttestationGate, UrgentDirectRestriction, TierOverrideJustification) |
| 242 | +are sound wrt a Lean4 model of the risk ladder: every envelope the |
| 243 | +ladder should reject is rejected, and every envelope the ladder should |
| 244 | +accept is accepted. |
| 245 | + |
| 246 | +*Artefact.* `proofs/RiskLadder.lean` — model of the ladder + contract |
| 247 | +equivalence theorem. Optional: translate back to Idris2 if we want a |
| 248 | +single-prover house. |
| 249 | + |
| 250 | +*Dependencies.* P-11. Prover tentative; Idris2 can also do this. |
| 251 | + |
| 252 | +*Acceptance.* Closes the Nickel-vs-Zig boundary question currently open |
| 253 | +in Appendix K. |
| 254 | + |
| 255 | +=== P-13 Zig ↔ Idris2 ABI correspondence [P2] |
| 256 | + |
| 257 | +*Claim.* The Zig constant `BIND_ADDR = [4]u8{127,1,0,1}` together with |
| 258 | +`BIND_PORT = 7745` faithfully realises `coordBindConfig` from |
| 259 | +`SafeLocalCoord.idr`. |
| 260 | + |
| 261 | +*Artefact.* An extraction-style check: Idris2 generates a C header |
| 262 | +declaring `BIND_ADDR`/`BIND_PORT`; Zig imports it. Any divergence is a |
| 263 | +compile error. |
| 264 | + |
| 265 | +*Dependencies.* P-00. |
| 266 | + |
| 267 | +*Acceptance.* Pure mechanical — turns a convention into a constraint. |
| 268 | + |
| 269 | +== Deferred (Phase 2+) |
| 270 | + |
| 271 | +=== P-15 Choreographic types |
| 272 | + |
| 273 | +Model the `supervised → server → {opus, attester} → server → target` |
| 274 | +protocol as a session type. Compile-time enforcement replaces run-time |
| 275 | +imperative checks in `coord_send_gated`. |
| 276 | + |
| 277 | +Reference: Appendix H in design log. |
| 278 | + |
| 279 | +=== P-16 Epistemic types |
| 280 | + |
| 281 | +Rename `context_fetch_id` to `knowledge_witness`; formalise per-role |
| 282 | +epistemic policy. Track what each peer knows, with K_A(φ) logic. |
| 283 | + |
| 284 | +Reference: Appendix H. |
| 285 | + |
| 286 | +=== P-17 Echo-type formalisation |
| 287 | + |
| 288 | +Use `echo-types/` bridges (`EchoChoreo`, `EchoEpistemic`, `EchoTropical`) |
| 289 | +to prove audit + summary + hash-chain behave as echo types — structured |
| 290 | +irreversibility with retained constraint. |
| 291 | + |
| 292 | +Reference: Appendix I. |
| 293 | + |
| 294 | +=== P-18 Tropical-semiring TTL/trust |
| 295 | + |
| 296 | +Model watchdog TTLs as tropical max, trust composition as tropical min. |
| 297 | +Integrates with P-17. |
| 298 | + |
| 299 | +Reference: Appendix J. |
| 300 | + |
| 301 | +=== P-19 Cross-site primacy ceremony (v2 federation) |
| 302 | + |
| 303 | +When Phase 4 lands, prove the authoritative-site model provides primacy |
| 304 | +transfer as a choreographic + epistemic ceremony. |
| 305 | + |
| 306 | +Reference: Appendix G. |
| 307 | + |
| 308 | +== Work schedule |
| 309 | + |
| 310 | +.P0 block — before Task #8 E2E sign-off |
| 311 | +1. P-04 (record format) — ~1 day — `abi/LocalCoord/Durability.idr`, smallest theorem, unlocks rest. |
| 312 | +2. P-05 (CRC truncation) — ~1 day — prefix-closure lemma on top of P-04. |
| 313 | +3. P-06 (replay-equivalence) — ~3 days — biggest single theorem, needs `State` encoding. Most of the value. |
| 314 | +4. P-07 (quarantine state machine) — ~1 day — simple state chart, well-isolated. |
| 315 | + |
| 316 | +.P1 block — before Task #13 track-record |
| 317 | +5. P-08 (FIFO) — ~1 day — standard Vect-indexed argument. |
| 318 | +6. P-09 (monotonicity) — ~0.5 day — corollary of P-05. |
| 319 | +7. P-10 (supervisor privilege) — ~1 day — phantom-witness refactor. |
| 320 | +8. P-11 (Tier-4 forbidden) — ~1 day — schema + Idris2 wrapper. |
| 321 | + |
| 322 | +.P2 block — nice-to-have; triggered by Task #17 Deno shim |
| 323 | +9. P-12 (Nickel soundness) — ~2 days. |
| 324 | +10. P-13 (Zig↔Idris2 correspondence) — ~0.5 day — mostly plumbing. |
| 325 | + |
| 326 | +.Phase 2+ — deferred; revisit when moving from v1 |
| 327 | +11. P-15 through P-19 — scheduled by federation triggers, not by calendar. |
| 328 | + |
| 329 | +Estimate totals: P0 ≈ 6 days, P1 ≈ 3.5 days, P2 ≈ 2.5 days. Comfortably |
| 330 | +one Claude-calendar-week of focused proof work if taken end-to-end. |
| 331 | + |
| 332 | +== Notes on prover choice |
| 333 | + |
| 334 | +Idris2 remains the default — proofs for a cartridge ABI belong next to |
| 335 | +the existing `SafeLocalCoord.idr` and `Protocol.idr`. Idris2 handles |
| 336 | +everything through P-13. |
| 337 | + |
| 338 | +Agda enters only for Phase 3 echo-type + tropical work, consuming the |
| 339 | +`echo-types/` bridge library. No echo-type obligation lives inside the |
| 340 | +cartridge repo — it lands upstream in `echo-types/` with coord as the |
| 341 | +dogfood consumer. |
| 342 | + |
| 343 | +Lean4 is listed as an alternative for P-12 only, and only if the user |
| 344 | +wants an external mathlib-backed model of the ladder; otherwise Idris2 |
| 345 | +does it. |
| 346 | + |
| 347 | +No part of the schedule needs Coq/Rocq or SPARK/Ada — those are |
| 348 | +reserved for upstream proof-heavy consumers (007, Cerro-Torre). |
| 349 | + |
| 350 | +== Cross-references |
| 351 | + |
| 352 | +* Already-proved obligations: `SafeLocalCoord.idr`, `Protocol.idr`. |
| 353 | +* Phase 2+/Phase 3 rationale: `Desktop/COORD-MCP-DESIGN-LOG.md` |
| 354 | + Appendices H, I, J, K. |
| 355 | +* Durability module: `../../ffi/coord_durability.zig`. |
| 356 | +* Envelope schema + contracts: |
| 357 | + `../../schemas/coord-messages.ncl`, `coord-messages-contracts.ncl`. |
0 commit comments