|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 3 | += Cluster C1 — four-gate evidence (captured 2026-06-05) |
| 4 | +:toc: macro |
| 5 | + |
| 6 | +[IMPORTANT] |
| 7 | +==== |
| 8 | +*Captured run of the 4-gate recipe over the eight cluster-C1 pure-integer |
| 9 | +kernels.* Every number here is reproducible from this directory; the |
| 10 | +re-run commands are in `README.adoc`. Toolchain: AffineScript compiler |
| 11 | +`_build/default/bin/main.exe`, Deno 2.8.2, Agda 2.6.3, echo mirror at |
| 12 | +`/tmp/mirror-boundary`. |
| 13 | +==== |
| 14 | + |
| 15 | +toc::[] |
| 16 | + |
| 17 | +== Summary |
| 18 | + |
| 19 | +[cols="2,1,1,1,1",options="header"] |
| 20 | +|=== |
| 21 | +| Kernel | G1 compile | G2 parity | G3 boundary | G4 assail |
| 22 | +| `DeviceType` | OK | 39/39 | LOSSLESS (12) | clean |
| 23 | +| `PuzzleFormat` | OK | 74/74 | CONTROLLED-LOSS @0 | clean |
| 24 | +| `PortNames` | OK | 52/52 | LOSSLESS (10) | clean |
| 25 | +| `GameEvent` | OK | 50/50 | LOSSLESS (6) + CL @0 | clean |
| 26 | +| `Kernel_Compute` | OK | 736/736 | (decision; see note) | clean |
| 27 | +| `Kernel` | OK | 19/19 | CONTROLLED-LOSS @4 | clean |
| 28 | +| `RetryPolicy` | OK | 124/124 | (decision; see note) | clean |
| 29 | +| `Diagnostics` | OK | 129/129 | (decision; see note) | clean |
| 30 | +| *Total* | *8/8* | *1223/1223* | *6 proofs, agda exit 0* | *8/8 clean* |
| 31 | +|=== |
| 32 | + |
| 33 | +== Gate 1 — compile (`main.exe compile … -o …`) |
| 34 | + |
| 35 | +All eight `.affine` sources lower to wasm, exit 0: |
| 36 | + |
| 37 | +---- |
| 38 | +[OK] DeviceType [OK] GameEvent [OK] RetryPolicy |
| 39 | +[OK] PuzzleFormat [OK] Kernel_Compute [OK] Diagnostics |
| 40 | +[OK] PortNames [OK] Kernel |
| 41 | +---- |
| 42 | + |
| 43 | +== Gate 2 — parity (oracle-vs-wasm, scalar i32 ABI) |
| 44 | + |
| 45 | +Each kernel swept against an *independent* JS oracle (re-derived in the |
| 46 | +`*.config.mjs`, never reusing AffineScript code) so a codegen regression |
| 47 | +surfaces as a differential mismatch. 1223 inputs, 0 mismatches. |
| 48 | + |
| 49 | +---- |
| 50 | +DeviceType ........ 39/39 Kernel_Compute ... 736/736 |
| 51 | +PuzzleFormat ...... 74/74 Kernel ........... 19/19 |
| 52 | +PortNames ......... 52/52 RetryPolicy ...... 124/124 |
| 53 | +GameEvent ......... 50/50 Diagnostics ...... 129/129 |
| 54 | +---- |
| 55 | + |
| 56 | +`Kernel_Compute.compute_gate` is the widest sweep: the full |
| 57 | +domain × active_calls × data_len × has_backend grid (10 × 4 × 9 × 2 = 720), |
| 58 | +exercising the 504 > 413 > 404 > 0 precedence at every boundary. |
| 59 | + |
| 60 | +== Gate 3 — echo-boundary (Agda-certified encoding faithfulness) |
| 61 | + |
| 62 | +Six host-value→code tables certified against the `EchoEncodingFaithfulness` |
| 63 | +framework; every generated module typechecks (`agda exit 0`). The generated |
| 64 | +proofs are staged next to each kernel (`*Boundary.agda`). |
| 65 | + |
| 66 | +[cols="2,1,3",options="header"] |
| 67 | +|=== |
| 68 | +| Module | Verdict | What it certifies |
| 69 | +| `DeviceTypeBoundary` | LOSSLESS | 12 device kinds → 12 distinct codes; host round-trips device ↔ code. |
| 70 | +| `PortNamesBoundary` | LOSSLESS | 10 coprocessor domains → ids 0..9; the portnames.wasm bridge contract. |
| 71 | +| `GameEventDirectionBoundary` | LOSSLESS | 6 directions → codes 0..5. |
| 72 | +| `PuzzleFormatTierBoundary` | CONTROLLED LOSS | DLCLoader's `parseTier` clamp: unknown numeral collapses to `Tier0` (code 0). Collision certified → no section. |
| 73 | +| `GameEventAlertBoundary` | CONTROLLED LOSS | ordered alert clamp: below-Green collapses to Green (code 0). Collision certified → no section. |
| 74 | +| `KernelRouteBoundary` | CONTROLLED LOSS | the router's compute fan-in: six domains share handler class 4. Deliberate forgetting, certified → no section. |
| 75 | +|=== |
| 76 | + |
| 77 | +*Note (decision kernels).* `Kernel_Compute.compute_gate`, |
| 78 | +`RetryPolicy.is_transient` and `Diagnostics.health_of` are intentionally |
| 79 | +many-to-one *decisions*, not encodings — they classify rather than name, so no |
| 80 | +injectivity is claimed for them and echo-boundary's encoding lens does not |
| 81 | +apply. Their faithfulness is established by Gate 2 (full-grid parity against an |
| 82 | +independent oracle). Where one of these kernels *does* carry an encoding (the |
| 83 | +shared domain code, the alert/tier taxonomies), that encoding is certified |
| 84 | +above. |
| 85 | + |
| 86 | +== Gate 4 — affine-assail (no undeclared clamp / unguarded decoder) |
| 87 | + |
| 88 | +All eight kernels scan clean (exit 0): |
| 89 | + |
| 90 | +---- |
| 91 | +[CLEAN] DeviceType [CLEAN] Kernel_Compute |
| 92 | +[CLEAN] PuzzleFormat [CLEAN] Kernel |
| 93 | +[CLEAN] PortNames [CLEAN] RetryPolicy |
| 94 | +[CLEAN] GameEvent [CLEAN] Diagnostics |
| 95 | +---- |
| 96 | + |
| 97 | +*On clamps and the gate-4 signal.* The in-band clamps in this cluster |
| 98 | +(`PuzzleFormat.clamp_tier`/`clamp_difficulty` → 0, `GameEvent.clamp_alert_level` |
| 99 | +→ 0/3) are written *guard-style* — an explicit validity test then the default — |
| 100 | +rather than as a catch-all `match` arm. assail's `PA-AFF-001` heuristic targets |
| 101 | +the match-arm shape (e.g. SecurityRank's `Invalid(v) => …`, which it correctly |
| 102 | +flags as a declared clamp); guard-style clamps present no catch-all arm and so |
| 103 | +scan clean. This is sound here because the controlled-loss of every one of these |
| 104 | +clamps is *independently certified by Gate 3* (the `…Boundary.agda` proofs |
| 105 | +above). Gate 3 (the clamp is controlled-loss-certified) and Gate 4 (no |
| 106 | +*undeclared* match-arm clamp) together account for every boundary in the |
| 107 | +cluster. The out-of-band *sentinels* (`-1` in DeviceType/PortNames/GameEvent/ |
| 108 | +PuzzleFormat) are not clamps — they map outside the in-band range, so no in-band |
| 109 | +collision is introduced and nothing is lost. |
| 110 | + |
| 111 | +== Host-side files (no pure-integer brain — the "senses") |
| 112 | + |
| 113 | +Three C1 files carry no extractable integer core and stay host-side: |
| 114 | + |
| 115 | +* `Coprocessor_Backends.res` — backend-registration wiring (ten `register()` |
| 116 | + side-effects; the order is the only datum, and it is fixed). |
| 117 | +* `PortNamesCoprocessor.res` — the JS-bridge binding shim that *consumes* |
| 118 | + `PortNames.affine` (→ portnames.wasm). The shim stays; the kernel it binds is |
| 119 | + delivered here. |
| 120 | +* `DLCLoader.res` — a JSON parser/serialiser (`Js.String2.*` / `Js.Json.*` |
| 121 | + throughout). Its one integer fragment, `parseTier`'s numeral clamp, is |
| 122 | + delivered as `PuzzleFormat.clamp_tier` (certified controlled-loss above); the |
| 123 | + rest is irreducibly string/JSON host work. (The Phase-B migratability tool |
| 124 | + bucketed this MIGRATABLE-NOW because it scans the modern `String.*` API, not |
| 125 | + the legacy `Js.String2.*` this file uses — recorded here as a triage erratum.) |
0 commit comments