Skip to content

Commit d706d52

Browse files
migration B+C: full-corpus triage + C1 deep wave (8 kernels, 4-gate green) (#535)
ReScript → AffineScript → wasm migration of `hyperpolymath/idaptik`, staged under `proposals/idaptik/` (the access gate keeps idaptik writes out of scope; this is *verified + staged + applyable*, not applied). Two phases on this branch. ## Phase C — deep wave 1 (cluster C1), 4-gate green Cluster C1 (the eleven `shared/src` leaf modules) re-decomposed into **8 pure-integer co-processor kernels** staged under `proposals/idaptik/migrated/<Name>/`. Brain = the integers that cross the wasm boundary; the host keeps strings/floats/DOM/mutable state (the senses). | Kernel | Brain | Boundary verdict | |---|---|---| | `DeviceType` | 12-device taxonomy: count, validity, code-clamp | LOSSLESS | | `PuzzleFormat` | difficulty(4)/tier(5) + DLCLoader clamp-to-default | CONTROLLED-LOSS @0 | | `PortNames` | 10 coprocessor-domain port taxonomy (= `portnames.wasm`) | LOSSLESS | | `GameEvent` | alertLevel(4, ordered clamp) + direction(6, flat) | LOSSLESS + CL @0 | | `Kernel_Compute` | per-domain element caps + 504/413/404/0 scheduler gate | decision (parity) | | `Kernel` | 503/404/0 pre-flight gate + domain→handler router | CONTROLLED-LOSS @4 | | `RetryPolicy` | 503/504/429 transient classifier + 3-policy table | decision (parity) | | `Diagnostics` | <70/<90/≥90 health-band classifier + registry check | decision (parity) | **Re-decomposed, not transliterated:** Promise/async collapsed to the synchronous status *decision*; service-locator reads (`ResourceAccounting`, backend registry) threaded as explicit integer params; variants re-encoded as closed integer bands; floats + wall-clock timing kept host-side. **Four gates green** (captured in `migrated/EVIDENCE.adoc`): - **G1 compile** — 8/8 → wasm - **G2 parity** — 1223/1223 oracle-vs-wasm inputs (independent JS oracles) - **G3 echo-boundary** — 6 Agda proofs, `agda exit 0`: 3 LOSSLESS + 3 CONTROLLED-LOSS, each collision named and certified - **G4 assail** — 8/8 clean (no undeclared clamp / unguarded decoder) Three C1 files are host-side "senses" with no brain and stay in the host: `Coprocessor_Backends.res` (registration wiring), `PortNamesCoprocessor.res` (JS bridge shim — consumes `PortNames.affine`), `DLCLoader.res` (JSON parser; its one integer fragment ships as `PuzzleFormat.clamp_tier`). ## Phase B — full-corpus triage (the worklist) `affine-migratability` over all **571** idaptik `.res` files → the resumable worklist Phases C–Ω navigate from. | Verdict | Count | % | |---|---|---| | **MIGRATABLE NOW** | 389 | 68% | | STRING-GATED (wall 1) | 71 | 12% | | EFFECT-GATED (wall 2) | 111 | 19% | Non-test production: 358 files, **196 migratable (55%)**. Twelve clusters C1..C12 ordered leaf-pure-integer-first. Top walls: module-level `ref()` (79), string subscript (44), `String.length` (30). - `migration-map.json` — machine worklist (resume rule + per-cluster files + per-file wall labels); C1 status → **DONE** - `migration-map.adoc` — human map - `{state-vs-digest,game-vs-deepspace}.svg` — Phase A determinism diagrams (not previously committed) ## Ledger Phase B **DONE**; Phase C wave 1 (C1) **DONE**. **NEXT: C2** — the `vm/lib/ocaml` instruction set (31 files, the pure-integer reversible VM opcodes), same 4-gate recipe. Rebased onto current `main` (incl. #534 codegen-deno WASI stub); #533 evangelist toolkit confirmed merged. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s --- _Generated by [Claude Code](https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3110b49 commit d706d52

30 files changed

Lines changed: 4184 additions & 4 deletions

proposals/MIGRATION-PLAN.adoc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,11 @@ Heuristic:
185185
[cols="1,1,3",options="header"]
186186
|===
187187
| Phase | State | Notes
188-
| Pre | DONE | #531 echo proof (merged); #532 migration practice + guide + proposals (merged); #533 evangelist toolkit (draft, CI green).
189-
| A | DONE | Plan + model-per-phase guidance written; determinism doc gained a latency-regimes appendix (verify-don't-transfer + game-vs-deep-space + the 2 SVGs); #533 green (draft, owner to merge). NEXT: PHASE B.
190-
| B | TODO | Full-corpus triage -> migration-map.{adoc,json}.
191-
| C+ | TODO | Deep waves via the 4-gate recipe.
188+
| Pre | DONE | #531 echo proof (merged); #532 migration practice + guide + proposals (merged); #533 evangelist toolkit (*merged* 2026-06-05).
189+
| A | DONE | Plan + model-per-phase guidance written; determinism doc gained a latency-regimes appendix (verify-don't-transfer + game-vs-deep-space + the 2 SVGs); #533 merged. NEXT: PHASE B.
190+
| B | DONE | Full-corpus triage complete (2026-06-05). 571 files: *389 MIGRATABLE NOW (68%)*, 71 STRING-GATED (12%), 111 EFFECT-GATED (19%). Non-test: 358 files, 196 migratable (55%). Clusters C1–C12 ordered, leaf-first. Worklist: `proposals/idaptik/migration-map.json`. NEXT: PHASE C — cluster 1 = C1 (shared/src, 11 files) + C2 (vm instructions, 31 files). Switch to *Opus* for re-decomposition.
191+
| C (C1) | DONE | Deep wave 1 complete (2026-06-05, Opus). Cluster C1 re-decomposed: *8 pure-integer kernels* staged under `proposals/idaptik/migrated/` (DeviceType, PuzzleFormat, PortNames, GameEvent, Kernel_Compute, Kernel, RetryPolicy, Diagnostics); 3 C1 files are host-side "senses" with no brain (Coprocessor_Backends, PortNamesCoprocessor, DLCLoader). *Four gates green:* 8/8 compile, 1223/1223 parity, 6 echo-boundary proofs (agda exit 0; 3 LOSSLESS + 3 CONTROLLED-LOSS), 8/8 assail-clean. Evidence: `migrated/EVIDENCE.adoc`. The per-shape recipe is now established (enum-taxonomy / status-gate / classifier / predicate). NEXT: wave 2 = C2.
192+
| C2+ | TODO | Remaining deep waves via the 4-gate recipe; next = *C2* (vm/lib/ocaml instruction set, 31 files — the pure-integer reversible VM opcodes). Opus for any novel re-decomposition; Sonnet for the now-rote gate passes (C1 set the pattern). Then C3..C12.
192193
| F+ | TODO | Compiler walls (string backend, then effects).
193194
| Ω | TODO (access-gated) | Cutover + ReScript extinction.
194195
|===
Lines changed: 96 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Compiled wasm is build output, regenerated by every affine-parity run
2+
# (compile:true) from the staged .affine + config. Not committed.
3+
*.wasm
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
//
3+
// DeviceType -- the device-type taxonomy co-processor, the pure-integer core
4+
// extracted from idaptik shared/src/DeviceType.res. Per the DESIGN-VISION
5+
// ("AffineScript is the brain, JS/Pixi the senses; they pass primitives across
6+
// the wasm boundary"), the JS host keeps every device-type STRING (portName,
7+
// fromString) and the per-device mutable defenceFlags record; AffineScript owns
8+
// only the canonical integer encoding of the twelve device kinds and its
9+
// validity.
10+
//
11+
// The ReScript original is a 12-constructor variant `t` plus a `portName`
12+
// switch and a `fromString` parser. We re-decompose the variant as the
13+
// canonical 0..11 integer the constructor order already implies, so the
14+
// taxonomy is a closed band and validity is one range test. The variant IS the
15+
// integer; no strings cross the boundary. (We use range guards rather than an
16+
// enum round-trip because, the band being contiguous, the round-trip would be
17+
// the identity -- the same flat shape PortNames.affine uses for its 0..9 band.)
18+
//
19+
//## Device-type encoding (the header contract for the JS host)
20+
// code device code device
21+
// 0 Laptop 6 Camera
22+
// 1 Desktop 7 AccessPoint
23+
// 2 Server 8 PatchPanel
24+
// 3 Router 9 PowerSupply
25+
// 4 Switch 10 PhoneSystem (PBX)
26+
// 5 Firewall 11 FibreHub
27+
// The encoding is LOSSLESS: twelve distinct kinds map to twelve distinct codes,
28+
// so the host round-trips device-type <-> code with no collision (certified by
29+
// echo-boundary, DeviceTypeBoundary.agda). A code outside 0..11 is not a device
30+
// type: is_valid_device_type reports 0 and clamp_device_type returns the
31+
// out-of-band sentinel -1 -- never an in-band code, so no in-band collision is
32+
// introduced (this is a sentinel, not a clamp; assail stays clean).
33+
34+
// The number of canonical device kinds in the taxonomy.
35+
pub fn device_type_count() -> Int { 12 }
36+
37+
// Whether a host integer names a defined device type. 1 = valid, 0 = out of band.
38+
pub fn is_valid_device_type(code: Int) -> Int {
39+
if code < 0 { 0 } else { if code > 11 { 0 } else { 1 } }
40+
}
41+
42+
// Canonicalise a host integer: identity on a valid 0..11 code, the out-of-band
43+
// sentinel -1 otherwise. -1 is not an in-band code, so out-of-band input can
44+
// never be confused with a real device type (this is a sentinel, not a clamp).
45+
pub fn clamp_device_type(code: Int) -> Int {
46+
if is_valid_device_type(code) == 1 { code } else { -1 }
47+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
{-# OPTIONS --safe --without-K #-}
2+
-- SPDX-License-Identifier: MPL-2.0
3+
-- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
--
5+
-- GENERATED by echo-boundary (boundary.mjs). Do not edit by hand.
6+
--
7+
-- This module certifies an @boundary encoding-faithfulness verdict for a
8+
-- concrete host-value -> integer-code table, by reusing the
9+
-- EchoEncodingFaithfulness framework. It follows the SHAPE of that module's
10+
-- worked Rank / rankCode / rankCode-injective instance.
11+
--
12+
-- Source table (host name -> code):
13+
-- {
14+
-- "Laptop": 0,
15+
-- "Desktop": 1,
16+
-- "Server": 2,
17+
-- "Router": 3,
18+
-- "Switch": 4,
19+
-- "Firewall": 5,
20+
-- "Camera": 6,
21+
-- "AccessPoint": 7,
22+
-- "PatchPanel": 8,
23+
-- "PowerSupply": 9,
24+
-- "PhoneSystem": 10,
25+
-- "FibreHub": 11
26+
-- }
27+
28+
module DeviceTypeBoundary where
29+
30+
open import Echo using (Echo)
31+
open import EchoImageFactorization using (Injective)
32+
open import EchoEncodingFaithfulness using (Encoding; module EncodingTheorems)
33+
34+
open import Data.Nat.Base using (ℕ)
35+
open import Data.Product.Base using (Σ; _,_; _×_; proj₁)
36+
open import Relation.Binary.PropositionalEquality
37+
using (_≡_; _≢_; refl)
38+
open import Relation.Nullary using (¬_)
39+
40+
41+
-- The host-side enumerated value (one constructor per table name).
42+
-- c0 = Laptop
43+
-- c1 = Desktop
44+
-- c2 = Server
45+
-- c3 = Router
46+
-- c4 = Switch
47+
-- c5 = Firewall
48+
-- c6 = Camera
49+
-- c7 = AccessPoint
50+
-- c8 = PatchPanel
51+
-- c9 = PowerSupply
52+
-- c10 = PhoneSystem
53+
-- c11 = FibreHub
54+
data Host : Set where
55+
c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 : Host
56+
57+
-- The integer code the pure kernel computes on.
58+
code : Host
59+
code c0 = 0
60+
code c1 = 1
61+
code c2 = 2
62+
code c3 = 3
63+
code c4 = 4
64+
code c5 = 5
65+
code c6 = 6
66+
code c7 = 7
67+
code c8 = 8
68+
code c9 = 9
69+
code c10 = 10
70+
code c11 = 11
71+
72+
-- Injectivity by exhaustive case analysis. The codes are literal
73+
-- numerals, so listing the diagonal refl clauses is enough: Agda's
74+
-- coverage checker discharges the off-diagonal pairs as absurd
75+
-- numeral equalities (mirrors rankCode-injective).
76+
code-injective : Injective code
77+
code-injective {c0} {c0} _ = refl
78+
code-injective {c1} {c1} _ = refl
79+
code-injective {c2} {c2} _ = refl
80+
code-injective {c3} {c3} _ = refl
81+
code-injective {c4} {c4} _ = refl
82+
code-injective {c5} {c5} _ = refl
83+
code-injective {c6} {c6} _ = refl
84+
code-injective {c7} {c7} _ = refl
85+
code-injective {c8} {c8} _ = refl
86+
code-injective {c9} {c9} _ = refl
87+
code-injective {c10} {c10} _ = refl
88+
code-injective {c11} {c11} _ = refl
89+
90+
encoding : Encoding
91+
encoding = record
92+
{ Source = Host
93+
; Code =
94+
; enc = code
95+
}
96+
97+
-- Re-export the lossless-half headline, instantiated and proved:
98+
-- every integer code determines its host value uniquely.
99+
module Theorems = EncodingTheorems encoding
100+
101+
boundary-lossless :
102+
(i : ℕ) (e₁ e₂ : Echo code i) proj₁ e₁ ≡ proj₁ e₂
103+
boundary-lossless = Theorems.encoding-lossless code-injective
104+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for DeviceType.affine (idaptik device-type taxonomy;
5+
// scalar i32 ABI). The oracle re-derives the 0..11 closed band in plain JS so a
6+
// codegen regression surfaces as a differential mismatch.
7+
const valid = (c) => c >= 0 && c <= 11;
8+
export default {
9+
affine: "DeviceType.affine",
10+
cases: [
11+
{ name: "device_type_count()", export: "device_type_count", args: [], oracle: () => 12 },
12+
{
13+
name: "is_valid_device_type over [-3..15]",
14+
export: "is_valid_device_type",
15+
args: [[-3, 15]],
16+
oracle: (c) => (valid(c) ? 1 : 0),
17+
},
18+
{
19+
name: "clamp_device_type over [-3..15]",
20+
export: "clamp_device_type",
21+
args: [[-3, 15]],
22+
oracle: (c) => (valid(c) ? c : -1),
23+
},
24+
],
25+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
//
3+
// Diagnostics -- the device-health band classifier + registry sanity check, the
4+
// pure-integer core extracted from idaptik shared/src/Diagnostics.res. The
5+
// ReScript original wraps raw float utilisation percentages and per-backend
6+
// stats into typed report structures and formatted strings; those reports and
7+
// strings are host-side (the senses). What IS pure integer is the threshold
8+
// classification `healthOf` and the `allDomainsRegistered` count check.
9+
//
10+
// We re-decompose `healthOf : float -> healthLevel` onto an integer utilisation
11+
// percentage 0..100 (the host floors the float before crossing the boundary --
12+
// Math.floor is a sense). The three bands and their cutoffs are unchanged.
13+
//
14+
//## Health band encoding (Diagnostics.healthLevel)
15+
// band level range
16+
// 0 Healthy pct < 70
17+
// 1 Warning 70 <= pct < 90
18+
// 2 Critical pct >= 90 (>= 95 is the hard 503 cutoff)
19+
// health_of is deliberately many-to-one (a whole range folds onto one band);
20+
// that is the intended classification, not an encoding, so no injectivity is
21+
// claimed for it. The expected full registry is exactly ten domain backends.
22+
23+
pub fn health_band_count() -> Int { 3 }
24+
pub fn expected_domain_count() -> Int { 10 }
25+
26+
// Classify an integer utilisation percentage into a health band. Negative input
27+
// (impossible from a real percentage, but total here) lands in Healthy; >=90
28+
// in Critical, mirroring the ReScript `< 70 / < 90 / else` ladder.
29+
pub fn health_of(pct: Int) -> Int {
30+
if pct < 70 { return 0; }
31+
if pct < 90 { return 1; }
32+
2
33+
}
34+
35+
// 1 iff all ten expected domain backends are registered (Diagnostics
36+
// .allDomainsRegistered: registeredBackendCount() == 10).
37+
pub fn all_domains_registered(count: Int) -> Int {
38+
if count == expected_domain_count() { 1 } else { 0 }
39+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for Diagnostics.affine (idaptik health-band classifier +
5+
// registry sanity check; scalar i32 ABI). Oracle re-derives the <70 / <90 / else
6+
// ladder and the ==10 registry check.
7+
export default {
8+
affine: "Diagnostics.affine",
9+
cases: [
10+
{ name: "health_band_count()", export: "health_band_count", args: [], oracle: () => 3 },
11+
{ name: "expected_domain_count()", export: "expected_domain_count", args: [], oracle: () => 10 },
12+
{
13+
name: "health_of over [-5..105]",
14+
export: "health_of",
15+
args: [[-5, 105]],
16+
oracle: (pct) => (pct < 70 ? 0 : pct < 90 ? 1 : 2),
17+
},
18+
{
19+
name: "all_domains_registered over [0..15]",
20+
export: "all_domains_registered",
21+
args: [[0, 15]],
22+
oracle: (count) => (count === 10 ? 1 : 0),
23+
},
24+
],
25+
};

0 commit comments

Comments
 (0)