@@ -985,8 +985,22 @@ Staged plan (ledger INT-03; each row = one gated PR):
985985 post-codegen step wraps it with the standard preview1->preview2
986986 adapter into a component; ownership section survival asserted;
987987 wasmtime component-run smoke. Codegen unchanged ⇒ reversible.
988- - S4: native `wasi:clocks` + environment + argv via preview2 (wasmtime
989- host-testable), replacing the preview1 shims behind the component path.
988+ - S4 (DONE, slices S4a clock + S4b env_count/arg_count): on-demand
989+ preview1 imports (`wasi_snapshot_preview1.clock_time_get`,
990+ `environ_sizes_get`, `args_sizes_get`) via the Effect_sites
991+ pre-scan, canonical-order indexing through
992+ `ctx.wasi_func_indices`. NOTE divergence from this ADR's original
993+ S4 wording ("native `wasi:clocks` + environment + argv via
994+ preview2"): the slice shipped as preview1-shim, NOT native
995+ preview2 calls. This is sufficient under the component path
996+ because both adapters bridge the preview1 imports to
997+ `wasi:clocks` / `wasi:cli` internally; switching the codegen
998+ to emit `wasi:clocks/monotonic-clock@0.2.x.now()` directly only
999+ pays off once preview2 is the default (deferred into S6c). The
1000+ ADR is amended here to record what actually shipped rather than
1001+ pretending S4 fulfilled the original wording. String accessors
1002+ (env_at/arg_at) need byte-level wasm IR (I32Load8U/I32Store8) for
1003+ buffer marshalling and remain a tracked follow-up.
9901004- S5: `wasi:filesystem` (open/read/write/close) — unblocks INT-06.
9911005- S6a (WIT export lifting, DONE): codegen emits a `_start : () -> ()`
9921006 shim that calls `main` and drops its i32 result whenever a
@@ -999,13 +1013,33 @@ Staged plan (ledger INT-03; each row = one gated PR):
9991013 the component (exit 0), the ownership section survives, and the
10001014 lift is asserted. Purely additive: reactor consumers + game-loop
10011015 hooks are byte-unchanged.
1002- - S6b: `wasi:sockets`.
1016+ - S6b (sockets on-ramp, DONE): new `net_shutdown(fd, how) -> Int`
1017+ builtin (Effect `Net`, reserved) lowers to
1018+ `wasi_snapshot_preview1.sock_shutdown` via the same on-demand
1019+ canonical-order pattern as S4a/S4b (`optional_wasi` entry 4 in
1020+ `lib/codegen.ml`). The command adapter bridges to
1021+ `wasi:sockets/tcp` internally without surfacing a host-side
1022+ `wasi:sockets/*` requirement; `tests/componentize/sockets_smoke.sh`
1023+ gates the lift + a clock+env+sock combo for canonical-order
1024+ regression. Real-host invoke under wasmtime exits 0 on
1025+ `net_shutdown(stdin, RDWR)` (ENOTSOCK dropped). Larger socket
1026+ primitives (recv/send/accept) need byte-level wasm IR for buffer
1027+ / network-address marshalling and remain a tracked follow-up
1028+ alongside env_at/arg_at.
10031029- S6c: flip the default wasm target to component and demote the
1004- preview1 stdout path to a named legacy target.
1030+ preview1 stdout path to a named legacy target. Bundled cleanup:
1031+ replace the on-demand preview1 shims emitted by S4a/S4b/S6b
1032+ (clock_time_get, environ_sizes_get, args_sizes_get,
1033+ sock_shutdown) with native preview2 calls
1034+ (`wasi:clocks/monotonic-clock`, `wasi:cli/environment`,
1035+ `wasi:sockets/tcp`) — this only pays off once preview2 is the
1036+ default, hence its placement here rather than as a standalone
1037+ pre-flip slice.
10051038"""
10061039consequences = """
10071040- End-state is one-way (the component model becomes the canonical wasm
1008- target) but reversible-in-progress: preview1 retained through S5.
1041+ target) but reversible-in-progress: preview1 retained as the default
1042+ through S6b; the flip happens at S6c.
10091043- S3..S6 are HARD-GATED on S2 (toolchain). Tracked as a follow-up
10101044 issue; this is disclosed, not hidden.
10111045- typed-wasm ownership-section ABI is unchanged; the coordination
@@ -1070,34 +1104,23 @@ Thread per-call-site effect rows from typecheck to codegen via a
10701104 `let`-RHS call's effect row ⊇ `Async`” via a table lookup, replacing
10711105 `is_async_prim_call`/`async_primitives`.
10721106- *Fallback / safety.* If the table has no entry for a site (e.g. a
1073- pre-typecheck embedder path, or a synthesised node), or the consumer
1074- detects a producer/consumer count-mismatch, [Effect_sites.is_async_call]
1075- returns false ⇒ the CPS transform simply does not fire for that call.
1076- The pre-S4 plan retained the structural recogniser as the fallback
1077- *until the table path was proven*; S4 (PR #278) retired the
1078- hardcoded `async_primitives` set, so the steady-state fallback is
1079- "no transform" — over-conservative, always sound.
1107+ pre-typecheck embedder path, or a synthesised node), codegen falls
1108+ back to the structural recogniser. The hardcoded set is retired only
1109+ once the table path is proven (final slice); over-conservative
1110+ fallback is always sound (= today's behaviour).
10801111
10811112Staged (ledger #234; each a gated PR, full `dune test --force` +
10821113wasm e2e):
1083- - S1 (ADR-016 + plan, PR #270): DONE — no code change.
1084- - S2a (`lib/effect_sites.ml` shared numbering, PR #275): DONE — pure,
1085- gate-neutral.
1086- - S2b (typecheck builds & returns the side-table, PR #276): DONE — no
1087- codegen behaviour change yet.
1088- - S3 (pipeline threads the table; codegen boundary predicate switches
1089- to the table with structural fallback; new e2e
1090- `tests/codegen/effect_async_boundary.affine` proving a *user-defined*
1091- `Async` fn triggers the transform — PR #277): DONE. All existing
1092- http_cps_* / http_response_reader stay green.
1093- - S4 (retire the hardcoded `async_primitives` set; boundary is now
1094- exactly `Effect_sites.is_async_call`; fallback remains for table-
1095- empty / count-mismatch only — PR #278): DONE.
1096-
1097- *Delivery status:* CLOSED 2026-05-19 end-to-end. Issue #234 closed
1098- completed (`hyperpolymath/affinescript#234`). The structural name set
1099- no longer exists; the boundary is single-sourced from the typecheck
1100- effect side-table via the shared `Effect_sites` ordinal.
1114+ - S1 (this): ADR-016 + plan. No code change.
1115+ - S2: `lib/effect_sites.ml` shared numbering + typecheck builds &
1116+ returns the side-table. NO codegen behaviour change (table built,
1117+ unused) — pure, gate-neutral.
1118+ - S3: pipeline threads the table; codegen boundary predicate switches
1119+ to the table with structural fallback; new e2e proving a
1120+ *user-defined* `Async` fn triggers the transform (the payoff). All
1121+ existing http_cps_* / http_response_reader stay green.
1122+ - S4: retire the hardcoded `async_primitives` set (fallback remains
1123+ for table-miss only); doc truthing.
11011124"""
11021125consequences = """
11031126- Generalises to user-defined `Async` functions; new async primitives
@@ -1283,166 +1306,3 @@ references = [
12831306 "docs/PACKAGING.adoc (INT-04; the JS-package half)",
12841307 "docs/specs/SETTLED-DECISIONS.adoc (ADR-019 section)",
12851308]
1286-
1287- [[adr]]
1288- id = "ADR-020"
1289- status = "proposed"
1290- date = "2026-05-23"
1291- title = "Ownership-section schema versioning (0xAF sentinel + u8 version)"
1292- context = """
1293- The `affinescript.ownership` Wasm custom section has been frozen at
1294- v1 since typed-wasm Level 10 closure (2026-04-19) and is emitted in
1295- identical form by AffineScript (`lib/codegen.ml`) and by ephapax
1296- (`hyperpolymath/ephapax/src/ephapax-wasm`). The Rust verifier in
1297- `hyperpolymath/typed-wasm` consumes it as the cross-compat target.
1298-
1299- V1 has *no version field*. Today this is fine because both
1300- producers are at v1 and the verifier expects v1 unconditionally.
1301- The first multi-producer ABI change — when L1–6 / L14–16 emission
1302- lands — will need version discrimination. Adding the version field
1303- *now*, while we are still at v1, is much cheaper than retrofitting
1304- it under load.
1305-
1306- This ADR is filed alongside the broader typed-wasm widening roadmap
1307- in `docs/specs/TYPED-WASM-ROADMAP.adoc` §"Tranche B".
1308- """
1309- decision = """
1310- Move from v1 (unversioned) to v2 (versioned, with sentinel):
1311-
1312- v1 payload:
1313- u32 entry_count
1314- entry*
1315-
1316- v2 payload:
1317- u8 version_tag ; 0xAF — "AffineScript Format"
1318- u8 version ; 0x02 for v2.0
1319- u32 entry_count
1320- entry* ; same entry shape as v1; no new entry fields in v2.0
1321-
1322- The 0xAF sentinel is byte-distinct from any plausible v1 entry-count
1323- low byte for a module with ≤ 0xAE entries — i.e. practically every
1324- module in existence. v1 readers fail cleanly on a v2 section (bad
1325- entry-count); v2 readers see the sentinel and dispatch.
1326-
1327- Coordinated landing protocol:
1328- 1. Land ADR-020 in `hyperpolymath/affinescript` (this repo).
1329- 2. Mirror ADR in `hyperpolymath/typed-wasm` (Rust verifier
1330- dispatches on sentinel; accepts both v1 and v2 during
1331- migration).
1332- 3. Mirror ADR in `hyperpolymath/ephapax` (second producer emits
1333- v2).
1334- 4. Verifier ships v2-parse FIRST. Producers stay on v1 emit.
1335- 5. Once verifier is deployed (in both OCaml + Rust paths),
1336- producers flip to v2 emit together.
1337- 6. After a deprecation window, v1-parse path is removed from
1338- verifier.
1339-
1340- The v2.0 entry shape is identical to v1 — this ADR introduces no
1341- new ownership kinds, no new per-entry fields. Future widening
1342- (e.g. v2.1 adding a region-id field per entry) is a separate ADR.
1343- """
1344- consequences = """
1345- - Forward-compatible: any future widening (L1–6 / L14–16 carriers,
1346- per-entry annotations) can negotiate via the version byte without
1347- another sentinel-or-no-sentinel war.
1348- - v1 callers fail cleanly on v2 sections (bad entry-count parse) —
1349- no silent corruption.
1350- - Single coordinated landing across three repos; complexity is in
1351- the coordination, not the bytes.
1352- - This decision is PROPOSED. Land or supersede explicitly; do not
1353- silently drift.
1354- - Cross-references the typed-wasm roadmap in
1355- `docs/specs/TYPED-WASM-ROADMAP.adoc` §"B1".
1356- """
1357- references = [
1358- "docs/specs/TYPED-WASM-INTERFACE.adoc",
1359- "docs/specs/TYPED-WASM-INTERFACE.a2ml",
1360- "docs/specs/TYPED-WASM-ROADMAP.adoc",
1361- "lib/codegen.ml (build_ownership_section, ~line 159)",
1362- "lib/tw_verify.ml (parse_ownership_section)",
1363- "hyperpolymath/typed-wasm (Rust verifier crate — coordination target)",
1364- "hyperpolymath/ephapax (second producer — coordination target)",
1365- ]
1366-
1367- [[adr]]
1368- id = "ADR-021"
1369- status = "proposed"
1370- date = "2026-05-23"
1371- title = "Multi-producer ABI coordination model for typed-wasm carriers"
1372- context = """
1373- typed-wasm is a separate, language-agnostic compilation target with
1374- its own repository (`hyperpolymath/typed-wasm`) and its own
1375- producers (today: AffineScript + ephapax; potentially more later).
1376- The `affinescript.ownership` custom section is the first
1377- multi-producer ABI we own.
1378-
1379- Today coordination happens by `Refs #N` across repositories and
1380- good intent. As the typed-wasm widening proposals land (ADR-020
1381- schema versioning, then carriers for L1–6 and L14–16), this
1382- informal model will break. The session-note record makes clear that
1383- each ABI conversation has been one-off and ad-hoc; the failure mode
1384- is two producers landing incompatible changes in parallel because
1385- nobody owns "the protocol".
1386-
1387- This ADR is filed alongside the typed-wasm widening roadmap in
1388- `docs/specs/TYPED-WASM-ROADMAP.adoc` §"Tranche D" — D1 explicitly.
1389- The reason to land *this* ADR before any concrete widening proposal
1390- is that the coordination model is cheap now (no code), expensive
1391- later (under conflicting pressure from multiple in-flight proposals).
1392- """
1393- decision = """
1394- Adopt a formal four-axis multi-producer coordination model:
1395-
1396- (1) Spec authority — which artefact is the source of record:
1397- - Today: `lib/tw_verify.ml` (OCaml) is the spec of record.
1398- - On C5.1 closure (INT-12 / CONV-05): authority flips to
1399- `hyperpolymath/typed-wasm` Rust verifier crate.
1400- - The flip is itself an ADR — does not happen silently.
1401- - Until the flip, OCaml binds; on conflict, OCaml wins.
1402-
1403- (2) Coordinated landing protocol:
1404- - All ABI-touching ADRs live in all three repos (originated in
1405- one, mirrored in the other two with `Mirrors hyperpolymath/X#N`
1406- cross-references).
1407- - Verifier always ships parse-support FIRST.
1408- - Producers flip to emit support together, in a coordinated
1409- window.
1410- - Deprecation: a removed format MUST have a deprecation window
1411- of at least the cycle between two coordinated landings.
1412-
1413- (3) Test parity protocol:
1414- - Every producer maintains a fixture corpus matching the
1415- typed-wasm cross-compat suite (INT-12 / C5.1).
1416- - Each ABI change requires a new fixture in the cross-compat
1417- suite from each producer.
1418- - Verifier accepts a producer's emit only if the cross-compat
1419- corpus is up-to-date for that producer.
1420-
1421- (4) Conflict resolution:
1422- - In-flight ABI proposals from different producers MUST be
1423- serialised — no parallel ABI ADRs.
1424- - The serialisation queue is owned by `hyperpolymath/typed-wasm`
1425- (today via issues; future a dedicated coordination ledger).
1426- - A proposal blocked behind another in the queue is paused, not
1427- refused.
1428- """
1429- consequences = """
1430- - Single coordination ledger replaces ad-hoc Refs trails.
1431- - ABI change cost goes up (more paperwork) but predictability goes
1432- way up.
1433- - Compatible with the existing Hypatia / gitbot rules (Hypatia
1434- DOC-FORMAT / STACK-SIGNAL apply; ISSUE-CLOSURE applies to the
1435- shared ADR mirrors).
1436- - This decision is PROPOSED. Owner ratification required before any
1437- ABI ADR (ADR-020 included) lands; ADR-020 is a "first test" of
1438- this model and its own landing exercises the protocol.
1439- - Cross-references the typed-wasm roadmap in
1440- `docs/specs/TYPED-WASM-ROADMAP.adoc` §"D1".
1441- """
1442- references = [
1443- "docs/specs/TYPED-WASM-INTERFACE.adoc",
1444- "docs/specs/TYPED-WASM-ROADMAP.adoc",
1445- ".claude/CLAUDE.md §Hypatia and gitbot-fleet standing rules",
1446- "hyperpolymath/typed-wasm (coordination target)",
1447- "hyperpolymath/ephapax (second producer — coordination target)",
1448- ]
0 commit comments