Skip to content

Commit a00f285

Browse files
Claude/lucid pascal 0x l5 x (#345)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent e76b5f9 commit a00f285

9 files changed

Lines changed: 393 additions & 522 deletions

File tree

.machine_readable/6a2/META.a2ml

Lines changed: 53 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -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
"""
10061039
consequences = """
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

10811112
Staged (ledger #234; each a gated PR, full `dune test --force` +
10821113
wasm 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
"""
11021125
consequences = """
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-
]

docs/ECOSYSTEM.adoc

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ made real; #228/ADR-014 module-qualified paths (estate port unblocker). |
8080
|*E* |typed-wasm convergence hardening (the transition runway): the
8181
AffineScript↔typed-wasm contract widened from L7+L10 toward full
8282
L1–6/L13–16 emitted-wasm enforcement; estate-wide re-validation (#235);
83-
effect-threaded async-boundary recogniser (#234, DELIVERED 2026-05-19 —
84-
ADR-016, S1..S4 / PRs #270/#275/#276/#277/#278); the #225/#160 convergence
83+
effect-threaded async-boundary recogniser (#234); the #225/#160 convergence
8584
ABI matured to "shared with Ephapax". |*planned* |Begins when D's substrate
8685
(INT-01..04, CORE-01) is closed; ends at a stable, multi-producer
8786
typed-wasm convergence ABI.
@@ -97,17 +96,6 @@ on the same footing as the other producers (ephapax). See the contract below.
9796

9897
[IMPORTANT]
9998
====
100-
This section is a *summary* of the contract. The *authoritative*
101-
formal specification is
102-
link:specs/TYPED-WASM-INTERFACE.adoc[specs/TYPED-WASM-INTERFACE.adoc]
103-
(+ machine-readable companion
104-
link:specs/TYPED-WASM-INTERFACE.a2ml[specs/TYPED-WASM-INTERFACE.a2ml]).
105-
The roadmap of what would make typed-wasm a *natural and optimal*
106-
compilation target is
107-
link:specs/TYPED-WASM-ROADMAP.adoc[specs/TYPED-WASM-ROADMAP.adoc].
108-
ADR-020 (schema versioning) and ADR-021 (multi-producer model) are
109-
filed as PROPOSED in `.machine_readable/6a2/META.a2ml`.
110-
11199
typed-wasm is a *separate, language-agnostic compilation target* with its own
112100
repository (`hyperpolymath/typed-wasm`), its own Idris2 proofs, its own
113101
ReScript surface, its own Rust verifier crate, and *its own other producers*
@@ -240,21 +228,30 @@ smoke `tests/componentize/command_smoke.sh` proves the lift, the
240228
ownership section survives, and `wasmtime run <component>` exits
241229
0 on real-host invoke. Purely additive: reactor consumers, the
242230
`__indirect_function_table` export, and game-loop hooks are
243-
byte-unchanged.** String accessors (env_at/arg_at) gated on a
231+
byte-unchanged.** **S6b (sockets on-ramp) DONE: new
232+
`net_shutdown(fd, how) -> Int` builtin (Effect `Net`, reserved)
233+
lowers to `wasi_snapshot_preview1.sock_shutdown` via the same
234+
on-demand canonical-order pattern as S4a/S4b (`optional_wasi`
235+
entry 4); the command adapter bridges to `wasi:sockets/tcp`
236+
internally without surfacing a host-side `wasi:sockets/*`
237+
requirement; `tests/componentize/sockets_smoke.sh` gates the
238+
lift + a clock+env+sock combo for canonical-order regression;
239+
wasmtime real-host invoke exits 0 on `net_shutdown(stdin, RDWR)`
240+
(ENOTSOCK errno is dropped). Larger socket primitives
241+
(recv/send/accept) require byte-level wasm IR for buffer /
242+
network-address marshalling — tracked alongside env_at/arg_at.**
243+
String accessors (env_at/arg_at) gated on a
244244
byte-level wasm-IR extension (I32Load8U/I32Store8 absent today)
245-
— tracked as the next slice before/with S5 filesystem. Remaining
246-
sub-slices: S5 (native clocks/env/argv via preview2), S6b
247-
(`wasi:sockets`), S6c (flip the default wasm target from preview1
248-
→ component). WIT world of record: `wit/affinescript.wit`
249-
bridges to `wasi:clocks`/`wasi:cli`. Real-host main-invoke deferred
250-
to S6 (WIT export-lifting / wasi:cli/run command shape).
251-
**S5 string accessors (env_at/arg_at) DONE: the wasm IR gained
252-
the byte-level load/store family (I32Load8U/I32Store8 + the full
253-
WebAssembly 1.0 §5.4.6 row, opcodes 0x2C..0x35 / 0x3A..0x3E);
254-
accessors lower to on-demand `environ_get`/`args_get` paired with
255-
the existing `*_sizes_get` import (dedup keeps each WASI import
256-
once even when both `*_count` and `*_at` are used).** WIT world
257-
of record: `wit/affinescript.wit`
245+
— tracked as the next slice before/with S5. Remaining sub-slices
246+
(per ADR-015 canonical numbering in META.a2ml): S5 — bring up
247+
`wasi:filesystem` (open/read/write/close), which unblocks INT-06
248+
(server-side runtime profile); S6c — flip the default wasm
249+
target from preview1 → component, bundled with the optional
250+
native-preview2 cleanup that replaces the on-demand preview1
251+
shims emitted by S4a/S4b/S6b with direct `wasi:clocks` /
252+
`wasi:cli` / `wasi:sockets` calls (only pays off once preview2
253+
is the default, hence the bundling). WIT world of record:
254+
`wit/affinescript.wit`
258255
|INT-04 |Publish compiler + runtime to JSR (then npm) |#181 |runtime
259256
packaging READY (affine-js + affinescript-tea JSR dry-run green;
260257
manual-only `publish-jsr.yml`; docs/PACKAGING.adoc). INT-01 dep

0 commit comments

Comments
 (0)