@polyengine/wasi 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/cli.js CHANGED
@@ -12,7 +12,8 @@
12
12
  // tuple<stream<u8>, future<result<_, error-code>>>` (the tcp-receive
13
13
  // tuple shape) and `stdout/stderr.write-via-stream: func(data:
14
14
  // stream<u8>) -> future<result<_, error-code>>` (the tcp-send shape:
15
- // the async method's promise IS the future source, amendment A12); exit
15
+ // the async method's promise IS the future source, per embedder-api.md
16
+ // §"Streams and futures"); exit
16
17
  // gains `exit-with-code: func(status-code: u8)`, and environment's cwd
17
18
  // getter is renamed `get-initial-cwd` (0.2 spells it `initial-cwd`).
18
19
  import { InputStream, OutputStream } from "./io.js";
@@ -36,27 +37,22 @@ function concat(chunks) {
36
37
  * `exit`'s WIT signature is `exit: func(status: result)` — `result` with no
37
38
  * type parameters, i.e. `result<_, _>`. Per contracts/embedder-api.md's value
38
39
  * table, a `result` in **parameter** (non-return) position is plain nested
39
- * data: `{ kind: "ok" } | { kind: "err" }` (the A10 family — this comment
40
- * and the impl carried the pre-A10 `tag` spelling until 2026-08-14, a
41
- * latent bug the direct-call unit tests masked), never a throw. Only a
40
+ * data: `{ kind: "ok" } | { kind: "err" }` (embedder-api.md §"Naming and
41
+ * casing" enum/variant case names are data, not `{tag}` wrappers),
42
+ * never a throw. Only a
42
43
  * function's own *return*-position result throws/rejects.
43
44
  */
44
45
  export function cli(options = {}) {
45
46
  const stdoutChunks = [];
46
47
  const stderrChunks = [];
47
- const passthrough = options.passthrough ?? false;
48
48
  let exited = false;
49
49
  let exitOk;
50
50
  let exitCode;
51
51
  const stdout = new OutputStream((chunk) => {
52
52
  stdoutChunks.push(chunk);
53
- if (passthrough)
54
- console.log(new TextDecoder().decode(chunk));
55
53
  });
56
54
  const stderr = new OutputStream((chunk) => {
57
55
  stderrChunks.push(chunk);
58
- if (passthrough)
59
- console.error(new TextDecoder().decode(chunk));
60
56
  });
61
57
  const captured = {
62
58
  stdout: () => concat(stdoutChunks),
@@ -67,12 +63,11 @@ export function cli(options = {}) {
67
63
  exitOk: () => exitOk,
68
64
  exitCode: () => exitCode,
69
65
  };
70
- /** 0.3 write-via-stream into a capture buffer (A12: the promise IS the future). */
71
- const captureViaStream = (chunks, mirror) => async (data) => {
66
+ /** 0.3 write-via-stream into a capture buffer (the promise IS the future — embedder-api.md §"Streams and futures"). */
67
+ const captureViaStream = (chunks) => async (data) => {
72
68
  for await (const chunk of data) {
73
69
  const bytes = chunk instanceof Uint8Array ? chunk : Uint8Array.from(chunk);
74
70
  chunks.push(bytes);
75
- mirror?.(new TextDecoder().decode(bytes));
76
71
  }
77
72
  return { kind: "ok" };
78
73
  };
@@ -139,10 +134,10 @@ export function cli(options = {}) {
139
134
  ],
140
135
  },
141
136
  "wasi:cli/stdout@0.3": {
142
- writeViaStream: captureViaStream(stdoutChunks, passthrough ? (t) => console.log(t) : undefined),
137
+ writeViaStream: captureViaStream(stdoutChunks),
143
138
  },
144
139
  "wasi:cli/stderr@0.3": {
145
- writeViaStream: captureViaStream(stderrChunks, passthrough ? (t) => console.error(t) : undefined),
140
+ writeViaStream: captureViaStream(stderrChunks),
146
141
  },
147
142
  "wasi:cli/terminal-input@0.3": { TerminalInput },
148
143
  "wasi:cli/terminal-output@0.3": { TerminalOutput },
package/esm/cli_stdio.js CHANGED
@@ -18,15 +18,16 @@
18
18
  // are SYNC WIT functions. Against capture buffers they degenerate to
19
19
  // their non-blocking forms (io.ts base classes, sync fast path); against
20
20
  // a REAL stdin/stdout they must genuinely wait, which parks the calling
21
- // wasm frame through the suspending kernel (embedder-api A1/A2/A14
22
- // io.ts marks the blocking declarations on the REGISTERED stream
23
- // prototypes; these duck-typed stream impls override the behavior, and
24
- // per A2 the mark relays). Consequences: guests linking the blocking
25
- // leaves auto-select jspi mode on V8 engines, and on engines without
26
- // JSPI a genuine wait raises a clean `NeedsJspi` at the park site. The
27
- // `@0.3` track has no such dependence — its stdio is stream-shaped and
21
+ // wasm frame through the suspending kernel (embedder-api.md §"The WASI
22
+ // parking kernel" — io.ts marks the blocking declarations on the
23
+ // REGISTERED stream prototypes; these duck-typed stream impls override
24
+ // the behavior, and the mark relays). Consequences: guests linking the
25
+ // blocking leaves auto-select jspi mode on V8 engines, and on engines
26
+ // without JSPI a genuine wait raises a clean `NeedsJspi` at the park site.
27
+ // The `@0.3` track has no such dependence — its stdio is stream-shaped and
28
28
  // async by construction (`read-via-stream` returns the tcp-receive
29
- // tuple; `write-via-stream`'s promise is the future source, A12).
29
+ // tuple; `write-via-stream`'s promise is the future source, per
30
+ // embedder-api.md §"Streams and futures").
30
31
  //
31
32
  // Semantics:
32
33
  //
@@ -110,7 +111,7 @@ export function cliStdio(options = {}) {
110
111
  const p2Stdout = new SinkOutputStream(stdoutSink);
111
112
  const p2Stderr = new SinkOutputStream(stderrSink);
112
113
  // 0.3 write-via-stream: drain the guest's stream to the sink; the
113
- // promise is the future source (A12).
114
+ // promise is the future source (embedder-api.md §"Streams and futures").
114
115
  const writeViaStream = (sink) => async (data) => {
115
116
  try {
116
117
  for await (const chunk of data) {
package/esm/clocks.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // `wasi:clocks@0.2` + `wasi:clocks@0.3` (contracts/embedder-api.md
2
- // §"WASI examination"; the D-1 union clock — tools/smoke-c0/REPORT.md
3
- // finding D-1: `monotonic-clock@0.3.0` exposes different function sets
4
- // across the consumer corpus (`wait-for` vs `now`+`wait-until`) at the SAME
5
- // version string — same track, divergent drafts, served by one union
6
- // provider per contracts/embedder-api.md §"Version canonicalization").
2
+ // §"WASI examination"; the union clock: `monotonic-clock@0.3.0` exposes
3
+ // different function sets across the consumer corpus (`wait-for` vs
4
+ // `now`+`wait-until`) at the SAME version string — same track, divergent
5
+ // drafts, served by one union provider per contracts/embedder-api.md
6
+ // §"Version canonicalization").
7
7
  // The @0.3 track also carries system-clock (0.3's wall-clock reshape) and
8
8
  // the type-only types interface, per the WASI 0.3.1 release WIT.
9
9
  import { Pollable } from "./io.js";
@@ -39,8 +39,7 @@ export function clocks(options = {}) {
39
39
  nanoseconds: 1_000_000,
40
40
  }),
41
41
  };
42
- // The D-1 union provider (C0-proven shape, tools/smoke-c0/leg2_exec_model.ts):
43
- // both drafts' functions live on the one `@0.3` track provider; per-leaf
42
+ // The union provider: both drafts' functions live on the one `@0.3` track provider; per-leaf
44
43
  // structural resolution (contracts/embedder-api.md §"Version
45
44
  // canonicalization") lets each consumer link only the subset it imports.
46
45
  const monotonic03 = {
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // SYNC BY CONSTRUCTION: every backend op uses node's `*Sync` API, so the
15
15
  // 0.2 track's sync WIT functions are served without parking — guests run
16
- // in plain callback mode, no JSPI required (the A14 marks stay off; see
16
+ // in plain callback mode, no JSPI required (the park-capable marks stay off; see
17
17
  // fs_provider.ts). The 0.3 track returns plain values from async funcs,
18
18
  // which the runtime accepts.
19
19
  //
@@ -13,8 +13,9 @@
13
13
  // for the enforcement site: it is the provider, not this backend).
14
14
  //
15
15
  // ASYNC BY CONSTRUCTION: every OPFS op returns a promise, so the 0.2
16
- // track's sync WIT descriptor methods are marked park-capable (A14, on
17
- // the per-call class prototypes — fs_provider.ts): a p2 guest that
16
+ // track's sync WIT descriptor methods are marked park-capable (embedder-api.md
17
+ // §"The WASI parking kernel", on the per-call class prototypes — fs_provider.ts):
18
+ // a p2 guest that
18
19
  // touches the filesystem parks through the suspending kernel and needs
19
20
  // JSPI; on engines without it a genuine wait raises `NeedsJspi` at the
20
21
  // park site. The 0.3 track is async in WIT and needs no parking. (The
package/esm/http.js CHANGED
@@ -23,17 +23,18 @@
23
23
  // VERSION KEYS: 0.3.x releases fold onto the `@0.3` compatibility track
24
24
  // (contracts/embedder-api.md §"Version canonicalization"), so the
25
25
  // default registration serves every released 0.3.x with one provider —
26
- // the same flagship track-key pattern as the rest of this package. The
27
- // pre-consolidation rc SNAPSHOTS (`0.3.0-rc-*`) are prereleases, which
28
- // resolve exact-only: a guest pinned to one names it via
29
- // `http({ version: "0.3.0-rc-..." })`, which re-keys the fragment at
30
- // that exact id instead.
26
+ // the same flagship track-key pattern as the rest of this package. A
27
+ // guest pinned to a pre-consolidation rc SNAPSHOT (`0.3.0-rc-*`, which
28
+ // resolves exact-only) is out of scope for this fragment; an embedder
29
+ // serving one re-keys the `imports` object it gets back from `http()`
30
+ // manually (documented escape, unaffected by this fragment's surface).
31
31
  //
32
32
  // Body/trailers plumbing is the same stream+future choreography the TCP
33
33
  // provider proved: constructors return `[resource, transmission-future]`
34
- // (the future is a Promise — amendment A12 lowers it as the future
35
- // source), `consume-body` returns `[stream<u8>, trailers-future]`, and
36
- // guest-abandoned streams are retired by the runtime's A13 machinery
34
+ // (the future is a Promise — embedder-api.md §"Streams and futures"
35
+ // lowers it as the future source), `consume-body` returns
36
+ // `[stream<u8>, trailers-future]`, and guest-abandoned streams are
37
+ // retired by the runtime's producer-cancellation machinery
37
38
  // (`ReadableStream` sources are cancel()ed, which aborts the underlying
38
39
  // fetch body).
39
40
  //
@@ -62,7 +63,8 @@
62
63
  // Error model: `client.send` and the fallible fields/options methods
63
64
  // throw branded `ComponentException`s whose payloads use the WIT case
64
65
  // names VERBATIM (`DNS-timeout`, `TLS-protocol-error`, `internal-error` —
65
- // A10: case names are data, kebab-case as written, including capitals).
66
+ // embedder-api.md §"Naming and casing": case names are data, kebab-case
67
+ // as written, including capitals).
66
68
  // Fetch failures are TypeErrors with prose; a small sniff table maps the
67
69
  // recognizable ones and everything else is `internal-error(message)`.
68
70
  import { ComponentException, isComponentException } from "@polyengine/protocol";
@@ -142,7 +144,6 @@ const decoder = new TextDecoder();
142
144
  */
143
145
  export function http(options = {}) {
144
146
  const onCall = options.onCall ?? (() => { });
145
- const v = options.version ?? HTTP_TRACK;
146
147
  const allowRequest = options.allowRequest;
147
148
  // --- fields -----------------------------------------------------------------
148
149
  class Fields {
@@ -713,8 +714,8 @@ export function http(options = {}) {
713
714
  }
714
715
  return {
715
716
  imports: {
716
- [`wasi:http/types@${v}`]: { Fields, Request, RequestOptions, Response },
717
- [`wasi:http/client@${v}`]: { send },
717
+ "wasi:http/types@0.3": { Fields, Request, RequestOptions, Response },
718
+ "wasi:http/client@0.3": { send },
718
719
  },
719
720
  Fields: Fields,
720
721
  Request: Request,
@@ -15,9 +15,9 @@ export class ExitError extends Error {
15
15
  this.name = "ExitError";
16
16
  }
17
17
  }
18
- // A9 brand: an exit unwind propagates out through the embedder and any host
18
+ // Brand: an exit unwind propagates out through the embedder and any host
19
19
  // frames in between, so it must be recognizable across runtime copies
20
- // (contracts/embedder-api.md §"Module identity", issue #83).
20
+ // (contracts/embedder-api.md §"Module identity and @polyengine/protocol", issue #83).
21
21
  defineBrand(ExitError.prototype, WASI_EXIT);
22
22
  /** `terminal-input`/`terminal-output` are opaque resources; never produced (no terminal). */
23
23
  export class TerminalInput {
@@ -12,7 +12,7 @@
12
12
  // track gets its OWN resource class per `makeFilesystem` call — a guest
13
13
  // links one track and never mixes instances.
14
14
  //
15
- // ERROR SHAPES (the A10 family, and the reason this file exists twice
15
+ // ERROR SHAPES (embedder-api.md §"Naming and casing", and the reason this file exists twice
16
16
  // over): 0.2's `error-code` is an ENUM — the err payload is the bare
17
17
  // kebab-case string ("no-entry") — while 0.3's is a VARIANT (it grew
18
18
  // `other(option<string>)`) — the payload is `{ kind: "no-entry" }`.
@@ -21,7 +21,7 @@
21
21
  // code passes through untouched; an unmapped throw would be a trap, so
22
22
  // the guards map everything.
23
23
  //
24
- // SYNC vs PARKING (A14). 0.2 descriptor methods are sync WIT functions.
24
+ // SYNC vs PARKING (embedder-api.md §"The WASI parking kernel"). 0.2 descriptor methods are sync WIT functions.
25
25
  // A sync backend (node) returns plain values from every op — no parking,
26
26
  // callback-mode guests work untouched. An async backend (OPFS) returns
27
27
  // promises, so every backend-touching 0.2 method is wrapped `suspending`
@@ -212,7 +212,8 @@ function hashIdentity(id) {
212
212
  };
213
213
  }
214
214
  const READ_CHUNK = 65536;
215
- /** 0.2 methods wrapped `suspending` for async backends (A14; module
215
+ /** 0.2 methods wrapped `suspending` for async backends (embedder-api.md
216
+ * §"The WASI parking kernel"; module
216
217
  * header). Everything that touches the backend — stream CONSTRUCTION
217
218
  * stays plain (the streams themselves park via io.ts's marks). */
218
219
  const PARKED_02 = [
@@ -652,7 +653,7 @@ export function makeFilesystem(backend, preopens, access = {}) {
652
653
  })();
653
654
  return [source, done];
654
655
  }
655
- /** The promise IS the future source (A12): drain the guest's stream. */
656
+ /** The promise IS the future source (embedder-api.md §"Streams and futures"): drain the guest's stream. */
656
657
  async writeViaStream(data, offset) {
657
658
  try {
658
659
  requireWritable(err03);
@@ -849,7 +850,7 @@ export function makeFilesystem(backend, preopens, access = {}) {
849
850
  }
850
851
  }
851
852
  // Async backends: mark the 0.2 track's backend-touching methods
852
- // park-capable on the freshly-minted prototype (module header; A14).
853
+ // park-capable on the freshly-minted prototype (module header; embedder-api.md §"The WASI parking kernel").
853
854
  if (!backend.isSync) {
854
855
  const proto = Descriptor02.prototype;
855
856
  for (const name of PARKED_02) {
@@ -10,16 +10,16 @@
10
10
  // `accept` returning `would-block` until `subscribe`'s pollable is ready,
11
11
  // datagram streams with `receive(max)`/`check-send`+`send` batches — and
12
12
  // wasi-libc emulates POSIX blocking by `pollable.block()` (the io.ts
13
- // parking kernel, A14). Socket byte I/O rides `wasi:io/streams@0.2`:
13
+ // parking kernel, embedder-api.md §"The WASI parking kernel"). Socket byte I/O rides `wasi:io/streams@0.2`:
14
14
  // wasi-libc links the NON-blocking `input-stream.read` + `subscribe`
15
15
  // (never `blocking-read`) and `check-write`/`write`/`blocking-flush` —
16
16
  // exactly the surfaces of io.ts's async-backed `FedInputStream` /
17
17
  // `SinkOutputStream`, which this module mints over connections. The
18
18
  // PARKING therefore happens in `Pollable.block`/`poll` and
19
- // `blocking-flush`, all already A14-marked: 0.2 socket guests need JSPI
19
+ // `blocking-flush`, all already park-capable-marked: 0.2 socket guests need JSPI
20
20
  // on V8 engines, like the 0.3 track's `listen`.
21
21
  //
22
- // 0.2's `error-code` is an ENUM (bare strings — the A10 rule), with a
22
+ // 0.2's `error-code` is an ENUM (bare strings — embedder-api.md §"Naming and casing"), with a
23
23
  // different vocabulary than 0.3's variant: it grew `unknown`,
24
24
  // `would-block`, `not-in-progress`, `concurrency-conflict`,
25
25
  // `new-socket-limit` and the name-lookup codes, and it lacks
@@ -314,9 +314,7 @@ export function sockets02(onCall) {
314
314
  }
315
315
  let conn;
316
316
  try {
317
- conn = state.listener.tryAccept === undefined
318
- ? undefined
319
- : state.listener.tryAccept();
317
+ conn = state.listener.tryAccept();
320
318
  }
321
319
  catch (e) {
322
320
  raise02(e, "tcp-socket.accept");
@@ -447,7 +445,7 @@ export function sockets02(onCall) {
447
445
  }
448
446
  if (this.#state === "listening" && listen !== undefined) {
449
447
  const l = listen.listener;
450
- return new Pollable(() => l.acceptReady === undefined ? true : l.acceptReady(), () => l.waitAccept === undefined ? Promise.resolve() : l.waitAccept());
448
+ return new Pollable(() => l.acceptReady(), () => l.waitAccept());
451
449
  }
452
450
  return new Pollable(); // no pending operation: ready
453
451
  }
@@ -498,9 +496,6 @@ export function sockets02(onCall) {
498
496
  const conn = this.#conn;
499
497
  if (this.#state !== "connected" || conn === undefined)
500
498
  return;
501
- if (conn.setKeepAlive === undefined) {
502
- throw err02("not-supported", "tcp-socket: no keep-alive control on this backend");
503
- }
504
499
  try {
505
500
  conn.setKeepAlive(this.#keepAliveEnabled, Number(this.#keepAliveIdleNs / 1000000n));
506
501
  }
@@ -612,7 +607,7 @@ export function sockets02(onCall) {
612
607
  this.#streams = streams;
613
608
  // OS-level (dis)connect, fire-and-forget: failures surface on the
614
609
  // first datagram op (doc comment above).
615
- if (remoteAddress !== undefined && conn.connect !== undefined) {
610
+ if (remoteAddress !== undefined) {
616
611
  void conn.connect({
617
612
  transport: "udp",
618
613
  hostname: ipHostname(remoteAddress),
@@ -623,7 +618,7 @@ export function sockets02(onCall) {
623
618
  }
624
619
  else if (remoteAddress === undefined && wasConnected) {
625
620
  try {
626
- conn.disconnect?.();
621
+ conn.disconnect();
627
622
  }
628
623
  catch {
629
624
  // Not connected at the OS level (compat backends).
@@ -713,11 +708,11 @@ export function sockets02(onCall) {
713
708
  return;
714
709
  try {
715
710
  if (this.#hopLimit !== undefined)
716
- conn.setTtl?.(this.#hopLimit);
711
+ conn.setTtl(this.#hopLimit);
717
712
  if (this.#recvBuffer !== undefined)
718
- conn.setRecvBufferSize?.(Number(this.#recvBuffer));
713
+ conn.setRecvBufferSize(Number(this.#recvBuffer));
719
714
  if (this.#sendBuffer !== undefined)
720
- conn.setSendBufferSize?.(Number(this.#sendBuffer));
715
+ conn.setSendBufferSize(Number(this.#sendBuffer));
721
716
  }
722
717
  catch (e) {
723
718
  raise02(e, "udp-socket (applying cached options)");
@@ -758,7 +753,7 @@ export function sockets02(onCall) {
758
753
  while (out.length < max) {
759
754
  let item;
760
755
  try {
761
- item = this.#conn.tryReceive === undefined ? undefined : this.#conn.tryReceive();
756
+ item = this.#conn.tryReceive();
762
757
  }
763
758
  catch (e) {
764
759
  raise02(e, "incoming-datagram-stream.receive");
@@ -777,7 +772,7 @@ export function sockets02(onCall) {
777
772
  subscribe() {
778
773
  onCall("incoming-datagram-stream.subscribe");
779
774
  const conn = this.#conn;
780
- return new Pollable(() => conn.receiveReady === undefined ? true : conn.receiveReady(), () => conn.waitReceive === undefined ? Promise.resolve() : conn.waitReceive());
775
+ return new Pollable(() => conn.receiveReady(), () => conn.waitReceive());
781
776
  }
782
777
  [Symbol.dispose]() {
783
778
  // The socket owns the OS resources; streams are views.
@@ -103,7 +103,7 @@ export function sockets03(onCall) {
103
103
  * the remote and `send` needs no explicit address. An unbound socket
104
104
  * implicitly binds to the family wildcard first (wasmtime parity).
105
105
  *
106
- * SUSPENDING (A1/A2): node's `dgram.connect` settles via callback one
106
+ * SUSPENDING (embedder-api.md §"The WASI parking kernel"): node's `dgram.connect` settles via callback one
107
107
  * tick later, so this sync WIT func parks the calling frame for that
108
108
  * tick — the same shape as tcp `listen`.
109
109
  */
@@ -134,9 +134,6 @@ export function sockets03(onCall) {
134
134
  }
135
135
  this.#applyCachedOptions();
136
136
  }
137
- if (this.#conn.connect === undefined) {
138
- throw componentError({ kind: "not-supported" }, "udp-socket.connect: this host's datagram backend has no connected mode");
139
- }
140
137
  try {
141
138
  await this.#conn.connect({
142
139
  transport: "udp",
@@ -155,7 +152,7 @@ export function sockets03(onCall) {
155
152
  throw componentError({ kind: "invalid-state" }, "udp-socket.disconnect: the socket is not connected");
156
153
  }
157
154
  try {
158
- this.#conn.disconnect?.();
155
+ this.#conn.disconnect();
159
156
  }
160
157
  catch (e) {
161
158
  throw mapPlatformError(e, "udp-socket.disconnect");
@@ -342,12 +339,12 @@ export function sockets03(onCall) {
342
339
  return;
343
340
  try {
344
341
  if (this.#hopLimit !== undefined)
345
- conn.setTtl?.(this.#hopLimit);
342
+ conn.setTtl(this.#hopLimit);
346
343
  if (this.#recvBuffer !== undefined) {
347
- conn.setRecvBufferSize?.(Number(this.#recvBuffer));
344
+ conn.setRecvBufferSize(Number(this.#recvBuffer));
348
345
  }
349
346
  if (this.#sendBuffer !== undefined) {
350
- conn.setSendBufferSize?.(Number(this.#sendBuffer));
347
+ conn.setSendBufferSize(Number(this.#sendBuffer));
351
348
  }
352
349
  }
353
350
  catch (e) {
@@ -511,11 +508,12 @@ export function sockets03(onCall) {
511
508
  * WIT: `listen: func() -> result<stream<tcp-socket>, error-code>` —
512
509
  * transitions to `listening` and returns the perpetual accept stream,
513
510
  * whose elements are connected `TcpSocket` resources (lowered as
514
- * `own<tcp-socket>` — amendment A13 destroys any element the guest
515
- * never takes, closing that accepted connection). An unbound socket
511
+ * `own<tcp-socket>` — embedder-api.md §"Streams and futures" destroys
512
+ * any element the guest never takes, closing that accepted connection).
513
+ * An unbound socket
516
514
  * implicitly binds to the family wildcard with an ephemeral port.
517
515
  *
518
- * SUSPENDING (embedder-api A1/A2, the wasi:io `block` kernel): the OS
516
+ * SUSPENDING (embedder-api.md §"The WASI parking kernel", the wasi:io `block` kernel): the OS
519
517
  * bind is deferred one event-loop turn by `net.Server.listen` (module
520
518
  * header), so this async method awaits the settle and the runtime
521
519
  * parks the calling guest frame for that one tick. Full listener
@@ -584,7 +582,8 @@ export function sockets03(onCall) {
584
582
  release();
585
583
  }
586
584
  })();
587
- // The A13 producer-cancellation hook: when the guest drops the
585
+ // The producer-cancellation hook (embedder-api.md §"Streams and
586
+ // futures"): when the guest drops the
588
587
  // stream while the loop above is PARKED in accept(), the runtime's
589
588
  // pump invokes this — closing the listener is what unparks the
590
589
  // accept (it rejects; classified fatal; the generator retires).
@@ -601,7 +600,8 @@ export function sockets03(onCall) {
601
600
  }
602
601
  /**
603
602
  * WIT: `send: func(data: stream<u8>) -> future<result<_, error-code>>`
604
- * — a sync func; the returned promise is the future source (A12).
603
+ * — a sync func; the returned promise is the future source
604
+ * (embedder-api.md §"Streams and futures").
605
605
  * NEVER throws: the function has no error channel of its own, so every
606
606
  * failure — including the state-machine ones — resolves the future as
607
607
  * an err value. The argument stream is dropped on failure so its
@@ -660,7 +660,7 @@ export function sockets03(onCall) {
660
660
  * close — the future distinguishes them (`ok` vs `err`). Dropping the
661
661
  * stream's reader (guest SHUT_RD) stops the pump, discards queued
662
662
  * data, and settles the future ok — the canceller is the observer
663
- * (the same logic as embedder-api A8's cancelRead ruling).
663
+ * (the same logic as embedder-api.md §"Streams and futures"'s cancelRead ruling).
664
664
  */
665
665
  receive() {
666
666
  onCall("tcp-socket.receive");
@@ -677,7 +677,8 @@ export function sockets03(onCall) {
677
677
  try {
678
678
  for (;;) {
679
679
  // The chunk is node's own buffer (no copy); it is borrowed by
680
- // the rendezvous until the peer takes it (A5), which is safe —
680
+ // the rendezvous until the peer takes it (embedder-api.md
681
+ // §"Streams and futures": round-trip idempotence), which is safe —
681
682
  // each read hands back a distinct buffer.
682
683
  let chunk;
683
684
  try {
@@ -823,9 +824,6 @@ export function sockets03(onCall) {
823
824
  const conn = this.#conn;
824
825
  if (this.#state !== "connected" || conn === undefined)
825
826
  return;
826
- if (conn.setKeepAlive === undefined) {
827
- throw componentError({ kind: "not-supported" }, "tcp-socket: this host's TCP backend has no keep-alive control");
828
- }
829
827
  try {
830
828
  conn.setKeepAlive(this.#keepAliveEnabled, Number(this.#keepAliveIdleNs / 1000000n));
831
829
  }
@@ -47,7 +47,7 @@
47
47
  // `'error'` arrive later). The seam exposes that settle as
48
48
  // `TcpListener.settled()`; the provider awaits it inside a
49
49
  // `suspending`-marked `listen`, parking the calling guest frame for
50
- // the one tick (embedder-api A1/A2 — the same kernel that serves
50
+ // the one tick (embedder-api.md §"The WASI parking kernel" — the same kernel that serves
51
51
  // wasi:io's sync `block`). Full listener fidelity follows: real
52
52
  // ephemeral addresses, real bind error codes.
53
53
  // * dgram receive is push-shaped (`'message'` events); the adapter
package/esm/io.js CHANGED
@@ -1,27 +1,24 @@
1
1
  // `wasi:io@0.2` — error, poll, streams (contracts/embedder-api.md
2
2
  // §"WASI examination").
3
3
  //
4
- // THE PARKING KERNEL. `pollable.block()`, `poll()` and `blocking-*` are
5
- // sync WIT functions that must genuinely wait — the one p2 idiom that
6
- // fights a JS host. This package used to ship always-ready stubs (the
7
- // retired "three-tier strategy", grounded in C0 finding #6: no consumer
8
- // leg ever called a pollable method) with real parking documented as
9
- // "never (c) in this package". Both halves of that ruling expired:
4
+ // THE PARKING KERNEL (embedder-api.md §"The WASI parking kernel").
5
+ // `pollable.block()`, `poll()` and `blocking-*` are sync WIT functions
6
+ // that must genuinely wait the one p2 idiom that fights a JS host.
10
7
  //
11
8
  // * the polymorph-iroh upstream-iroh consumer class (unmodified
12
9
  // iroh/tokio) parks its reactor in `poll()` with timer + socket
13
- // pollables — the always-ready stubs don't degrade for such a guest,
14
- // they LIVELOCK it (block() no-ops, reads return empty, the frame
10
+ // pollables — an always-ready stub wouldn't degrade for such a guest,
11
+ // it would LIVELOCK it (block() no-ops, reads return empty, the frame
15
12
  // never suspends, so the event loop never turns and no host pump can
16
13
  // ever make progress);
17
- // * the runtime's suspending-import machinery (embedder-api.md A1/A2)
18
- // made real parking a per-declaration capability with graceful
19
- // degradation, so the kernel is ALWAYS ON rather than an opt-in
20
- // profile: on engines without JSPI, `chooseMode` falls back to plain
21
- // and everything behaves like the old stubs until a guest genuinely
22
- // parks — which then raises a clean `NeedsJspi` at the park site
23
- // instead of livelocking. Embedders wanting guaranteed-plain
24
- // instantiation pass `jspi: false`.
14
+ // * the runtime's suspending-import machinery (embedder-api.md
15
+ // §"Functions and async") makes real parking a per-declaration
16
+ // capability with graceful degradation, so the kernel is ALWAYS ON
17
+ // rather than an opt-in profile: on engines without JSPI, `chooseMode`
18
+ // falls back to plain and everything behaves like an always-ready stub
19
+ // until a guest genuinely parks — which then raises a clean
20
+ // `NeedsJspi` at the park site instead of livelocking. Embedders
21
+ // wanting guaranteed-plain instantiation pass `jspi: false`.
25
22
  //
26
23
  // Costs, deliberately confined: only the park-capable declarations are
27
24
  // marked (`block`, `poll`) — hot-path `read`/`check-write` stay plain —
@@ -36,15 +33,17 @@
36
33
  // wake pattern is polymorph-iroh's shim (promise-swap edge triggering).
37
34
  //
38
35
  // Streams: `read`/`check-write` stay plain (sync, never park), but the
39
- // `blocking-*` declarations are MARKED park-capable (amendment A14): the
36
+ // `blocking-*` declarations are MARKED park-capable (embedder-api.md
37
+ // §"The WASI parking kernel"): the
40
38
  // buffer-backed base impls below always take the sync fast path, while
41
39
  // the genuinely-async impls — `FedInputStream`/`SinkOutputStream` below,
42
40
  // serving cli-stdio's host stdin/stdout and filesystem-web's OPFS files,
43
41
  // where "blocking" cannot be served from a buffer — return a Promise and
44
42
  // park the frame. Marking follows the WIT declaration on the REGISTERED
45
- // class's prototype (A2: instance-level overrides change behavior, not
46
- // suspendability), which is what lets the duck-typed async streams park
47
- // through the resource types registered here.
43
+ // class's prototype (embedder-api.md §"Functions and async"
44
+ // "prototype declares, instances behave": instance-level overrides
45
+ // change behavior, not suspendability), which is what lets the
46
+ // duck-typed async streams park through the resource types registered here.
48
47
  var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
49
48
  var useValue = arguments.length > 2;
50
49
  for (var i = 0; i < initializers.length; i++) {
@@ -108,8 +107,9 @@ export class IoError {
108
107
  * A pollable over host-supplied readiness.
109
108
  *
110
109
  * WIT-facing surface: `ready()` and `block()` (the latter parks the
111
- * calling wasm frame when unready — @suspending, embedder-api.md A2:
112
- * the class prototype is the brand authority).
110
+ * calling wasm frame when unready — @suspending, embedder-api.md
111
+ * §"Functions and async": the class prototype is the brand authority
112
+ * ("prototype declares, instances behave").
113
113
  *
114
114
  * Host-facing surface: the constructor and `waitPromise()`. `ready` must
115
115
  * be cheap and side-effect-free; `wait` returns a promise that settles
@@ -135,7 +135,7 @@ let Pollable = (() => {
135
135
  constructor(ready = () => true, wait = () => Promise.resolve()) {
136
136
  this.#ready = ready;
137
137
  this.#wait = wait;
138
- // A20 realm-local pill (contracts/embedder-api.md §"Realm boundaries
138
+ // realm-local pill (contracts/embedder-api.md §"Realm boundaries
139
139
  // and structured-clone-safe forms"; issue #131): stateful handles fail
140
140
  // loud at a raw structuredClone/postMessage instead of husking.
141
141
  defineRealmLocal(this);
@@ -190,7 +190,7 @@ let Pollable = (() => {
190
190
  };
191
191
  })();
192
192
  export { Pollable };
193
- // A9 brand (contracts/embedder-api.md §"Module identity"): pollables cross
193
+ // brand (contracts/embedder-api.md §"Module identity and @polyengine/protocol"): pollables cross
194
194
  // into host provider code, which may resolve a different @polyengine copy. The
195
195
  // brand makes them recognizable there; same-copy `instanceof` is unchanged
196
196
  // and stays the documented spelling (issue #83). `poll()` itself needs no
@@ -275,14 +275,14 @@ let InputStream = (() => {
275
275
  }
276
276
  return out;
277
277
  }
278
- /** Park-capable (A14): the buffer-backed base never parks. */
278
+ /** Park-capable: the buffer-backed base never parks. */
279
279
  blockingRead(len) {
280
280
  return this.read(len);
281
281
  }
282
282
  skip(len) {
283
283
  return BigInt(this.read(len).length);
284
284
  }
285
- /** Park-capable (A14): the buffer-backed base never parks. */
285
+ /** Park-capable: the buffer-backed base never parks. */
286
286
  blockingSkip(len) {
287
287
  return this.skip(len);
288
288
  }
@@ -331,7 +331,7 @@ let OutputStream = (() => {
331
331
  throw closedError();
332
332
  this.#sink(contents);
333
333
  }
334
- /** Park-capable (A14): the never-backpressured base never parks. */
334
+ /** Park-capable: the never-backpressured base never parks. */
335
335
  blockingWriteAndFlush(contents) {
336
336
  this.write(contents);
337
337
  }
@@ -339,7 +339,7 @@ let OutputStream = (() => {
339
339
  if (this.#closed)
340
340
  throw closedError();
341
341
  }
342
- /** Park-capable (A14): the never-backpressured base never parks. */
342
+ /** Park-capable: the never-backpressured base never parks. */
343
343
  blockingFlush() {
344
344
  this.flush();
345
345
  }
@@ -349,7 +349,7 @@ let OutputStream = (() => {
349
349
  writeZeroes(len) {
350
350
  this.write(new Uint8Array(Number(len)));
351
351
  }
352
- /** Park-capable (A14): the never-backpressured base never parks. */
352
+ /** Park-capable: the never-backpressured base never parks. */
353
353
  blockingWriteZeroesAndFlush(len) {
354
354
  this.writeZeroes(len);
355
355
  }
@@ -358,7 +358,7 @@ let OutputStream = (() => {
358
358
  this.write(chunk);
359
359
  return BigInt(chunk.length);
360
360
  }
361
- /** Park-capable (A14): the never-backpressured base never parks. */
361
+ /** Park-capable: the never-backpressured base never parks. */
362
362
  blockingSplice(src, len) {
363
363
  return this.splice(src, len);
364
364
  }
@@ -380,7 +380,7 @@ export const STREAM_HIGH_WATER = 65536;
380
380
  * generic bridge from any `AsyncIterable<Uint8Array>` (host stdin, an
381
381
  * OPFS file read) to p2 stream semantics. `read` on an empty open stream
382
382
  * returns an empty list (p2's non-blocking contract), `blocking-read`
383
- * parks until bytes or EOF (A14/A2 mark relay — duck-typed against the
383
+ * parks until bytes or EOF (mark relay — duck-typed against the
384
384
  * registered `InputStream`, the marks relay from that prototype), and
385
385
  * EOF-with-drained-buffer is the `closed` stream-error. The feed pauses
386
386
  * past the high-water mark (no unbounded buffering).
@@ -481,7 +481,7 @@ let FedInputStream = (() => {
481
481
  throw closedError(); // drained + ended = closed
482
482
  return new Uint8Array(0); // open, nothing available: p2 non-blocking read
483
483
  }
484
- /** Parks (A14/A2 mark relay from the registered prototype). */
484
+ /** Parks (mark relay from the registered prototype). */
485
485
  blockingRead(len) {
486
486
  if (this.#buffered > 0 || this.#eof || this.#closed)
487
487
  return this.read(len);
@@ -517,7 +517,7 @@ export { FedInputStream };
517
517
  * budget: `check-write` reports the remaining permit (writing past it is
518
518
  * the guest's contract violation and traps via unbranded throw),
519
519
  * `blocking-flush`/`blocking-write-and-flush` park until the sink
520
- * drained everything (A14/A2 mark relay), `subscribe` wakes when budget
520
+ * drained everything (mark relay), `subscribe` wakes when budget
521
521
  * frees. A sink failure surfaces as the `last-operation-failed`
522
522
  * stream-error carrying an `IoError`.
523
523
  *
@@ -602,7 +602,7 @@ let SinkOutputStream = (() => {
602
602
  flush() {
603
603
  this.#checkOpen();
604
604
  }
605
- /** Parks until the sink drained everything (A14/A2 mark relay). */
605
+ /** Parks until the sink drained everything (mark relay). */
606
606
  blockingFlush() {
607
607
  this.#checkOpen();
608
608
  if (this.#queued === 0)
package/esm/mod.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // `@polyengine/wasi` — the WASI providers for polyengine hosts, and the
2
2
  // executable check that the embedder conventions
3
- // (`@polyengine/protocol`, amendment A22 — this package is protocol-only)
4
- // serve WASI (contracts/embedder-api.md C2
5
- // checklist item 7; docs/architecture.md §2 keeps implementations out of
3
+ // (`@polyengine/protocol` — this package is protocol-only, per
4
+ // embedder-api.md §"The host-ABI surface and its version")
5
+ // serve WASI (docs/architecture.md §2 keeps implementations out of
6
6
  // the RUNTIME — this package is where they live). Scope: p2
7
7
  // baseline + p3 clocks + à la carte sockets on BOTH tracks (the
8
8
  // poll-shaped `@0.2` surface std::net links, and `@0.3` UDP + TCP
@@ -54,9 +54,9 @@
54
54
  // canonicalization" (`@0.2`, `@0.3`) — this package is the flagship
55
55
  // track-key-registration consumer: one `@0.2` provider serves every p2
56
56
  // leaf regardless of whether the guest's binary says `0.2.6`, `0.2.9` or
57
- // `0.2.12` (C0 finding D-2), and one `@0.3` union provider serves both
57
+ // `0.2.12`, and one `@0.3` union provider serves both
58
58
  // divergent `monotonic-clock@0.3.0` drafts the corpus actually links
59
- // (C0 finding D-1).
59
+ // (§"Version canonicalization").
60
60
  import { cli } from "./cli.js";
61
61
  import { clocks } from "./clocks.js";
62
62
  import { filesystem } from "./filesystem.js";
package/esm/sockets.js CHANGED
@@ -101,11 +101,11 @@
101
101
  // once from `unbound` (a failed attempt closes the socket); `listen` once
102
102
  // from `unbound` (implicit wildcard-ephemeral bind) or `bound`;
103
103
  // `send`/`receive` once each, only when `connected`, and their failures
104
- // NEVER throw — `send`'s error channel is its returned future (amendment
105
- // A12: the async method's promise IS the future source) and `receive`'s
106
- // is the future half of its tuple, resolved as result values. `listen`
107
- // returns the perpetual accept stream, whose elements are connected
108
- // `tcp-socket` resources (amendment A13: un-taken elements are destroyed
104
+ // NEVER throw — `send`'s error channel is its returned future (embedder-api.md
105
+ // §"Streams and futures": the async method's promise IS the future source)
106
+ // and `receive`'s is the future half of its tuple, resolved as result values.
107
+ // `listen` returns the perpetual accept stream, whose elements are connected
108
+ // `tcp-socket` resources (§"Streams and futures": un-taken elements are destroyed
109
109
  // at teardown, closing their connections); per-connection accept failures
110
110
  // are skipped, listener-fatal ones end the stream. Stream teardown
111
111
  // follows the WIT's shared-ownership note: the OS socket closes only when
@@ -117,8 +117,8 @@
117
117
  // failures while consuming `send`'s stream (a peer trap) are NOT socket
118
118
  // errors: they propagate as producer failures on the host-failure channel.
119
119
  //
120
- // `listen` is SUSPENDING (embedder-api A1/A2 the wasi:io `block`
121
- // kernel): node defers the OS bind one event-loop turn, so `listen` parks
120
+ // `listen` is SUSPENDING (embedder-api.md §"The WASI parking kernel"
121
+ // the wasi:io `block` kernel): node defers the OS bind one event-loop turn, so `listen` parks
122
122
  // the calling guest frame for that tick and returns fully settled — real
123
123
  // ephemeral addresses from `get-local-address`, real error codes
124
124
  // (`address-in-use`) from a failed bind. Guests that link `listen`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polyengine/wasi",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "WASI providers for polyengine hosts: the p2 baseline and p3 clocks, one module per semver track.",
5
5
  "homepage": "https://github.com/polymorph-components/polyengine#readme",
6
6
  "repository": {
@@ -91,7 +91,7 @@
91
91
  "access": "public"
92
92
  },
93
93
  "dependencies": {
94
- "@polyengine/protocol": "^0.2.3"
94
+ "@polyengine/protocol": "^0.3.0"
95
95
  },
96
96
  "_generatedBy": "dnt@0.43.2"
97
97
  }
package/types/cli.d.ts CHANGED
@@ -8,8 +8,6 @@ export interface CliOptions {
8
8
  cwd?: string;
9
9
  /** `get-stdin`'s buffer contents; default empty (matches contract: "stdin (empty)"). */
10
10
  stdinBuffer?: Uint8Array;
11
- /** Also `console.log`/`console.error` captured stdout/stderr writes. Default false. */
12
- passthrough?: boolean;
13
11
  /** `exit()` throws `ExitError` instead of merely recording. Default false. */
14
12
  throwOnExit?: boolean;
15
13
  }
@@ -36,9 +34,9 @@ export interface CliResult {
36
34
  * `exit`'s WIT signature is `exit: func(status: result)` — `result` with no
37
35
  * type parameters, i.e. `result<_, _>`. Per contracts/embedder-api.md's value
38
36
  * table, a `result` in **parameter** (non-return) position is plain nested
39
- * data: `{ kind: "ok" } | { kind: "err" }` (the A10 family — this comment
40
- * and the impl carried the pre-A10 `tag` spelling until 2026-08-14, a
41
- * latent bug the direct-call unit tests masked), never a throw. Only a
37
+ * data: `{ kind: "ok" } | { kind: "err" }` (embedder-api.md §"Naming and
38
+ * casing" enum/variant case names are data, not `{tag}` wrappers),
39
+ * never a throw. Only a
42
40
  * function's own *return*-position result throws/rejects.
43
41
  */
44
42
  export declare function cli(options?: CliOptions): CliResult;
package/types/http.d.ts CHANGED
@@ -6,7 +6,7 @@ import { type Stream } from "@polyengine/protocol";
6
6
  * track literally, but the public entry point is `http()`.
7
7
  */
8
8
  export declare const HTTP_TRACK = "0.3";
9
- /** `method` — case names verbatim (A10). */
9
+ /** `method` — case names verbatim (embedder-api.md §"Naming and casing"). */
10
10
  export type Method = {
11
11
  kind: "get";
12
12
  } | {
@@ -94,13 +94,6 @@ export type BodySource = Stream<number> | AsyncIterable<Uint8Array | number[]> |
94
94
  /** What future params accept: the lifted handle or a promise of the value. */
95
95
  export type FutureLike<T> = PromiseLike<T>;
96
96
  export interface HttpOptions {
97
- /**
98
- * Override the registration keys for a guest pinned to a PRERELEASE
99
- * snapshot (`0.3.0-rc-*`), which the resolver matches exactly — no
100
- * track exists for prereleases. Default: the `@0.3` track, serving
101
- * every released 0.3.x.
102
- */
103
- version?: string;
104
97
  /** Observe every entry point the guest reaches (see sockets' onCall). */
105
98
  onCall?: (call: string) => void;
106
99
  /**
@@ -1,8 +1,7 @@
1
1
  import type { Stream } from "@polyengine/protocol";
2
- /** `wasi:cli/types@0.3`'s `error-code` ENUM: bare kebab-case strings (the
3
- * A10 value table — enums are data strings, not `{kind}` variants; this
4
- * type carried a `{kind}` wrapper until 2026-08-14, a latent bug no err
5
- * path had exercised). */
2
+ /** `wasi:cli/types@0.3`'s `error-code` ENUM: bare kebab-case strings
3
+ * (embedder-api.md §"Naming and casing" — enums are data strings, not
4
+ * `{kind}` variants). */
6
5
  export type CliErrorCode = "io" | "illegal-byte-sequence" | "pipe";
7
6
  /** `result<_, error-code>` AS A VALUE (the 0.3 stdio futures). */
8
7
  export type CliIoResult = {
@@ -123,7 +123,7 @@ export interface MetadataHashValue {
123
123
  lower: bigint;
124
124
  upper: bigint;
125
125
  }
126
- /** 0.3 `result<_, error-code>` as a future/tuple VALUE (A12 shapes). */
126
+ /** 0.3 `result<_, error-code>` as a future/tuple VALUE (embedder-api.md §"Streams and futures"). */
127
127
  export type FsResult03 = {
128
128
  kind: "ok";
129
129
  } | {
@@ -17,25 +17,23 @@ export interface DatagramConn {
17
17
  send(p: Uint8Array, addr?: NetAddr): Promise<number>;
18
18
  receive(): Promise<[Uint8Array, NetAddr]>;
19
19
  close(): void;
20
- /** OS-level connected mode (kernel filters + default destination).
21
- * Optional capability: absent = the provider answers `not-supported`. */
22
- connect?(addr: NetAddr): Promise<void>;
23
- disconnect?(): void;
24
- /** IP_TTL / IPV6_UNICAST_HOPS. Optional capability. */
25
- setTtl?(ttl: number): void;
26
- /** SO_RCVBUF / SO_SNDBUF. Optional capabilities. */
27
- getRecvBufferSize?(): number;
28
- setRecvBufferSize?(size: number): void;
29
- getSendBufferSize?(): number;
30
- setSendBufferSize?(size: number): void;
20
+ /** OS-level connected mode (kernel filters + default destination). */
21
+ connect(addr: NetAddr): Promise<void>;
22
+ disconnect(): void;
23
+ /** IP_TTL / IPV6_UNICAST_HOPS. */
24
+ setTtl(ttl: number): void;
25
+ /** SO_RCVBUF / SO_SNDBUF. */
26
+ getRecvBufferSize(): number;
27
+ setRecvBufferSize(size: number): void;
28
+ getSendBufferSize(): number;
29
+ setSendBufferSize(size: number): void;
31
30
  /** Non-blocking queue access + readiness (the 0.2 datagram streams:
32
- * poll-shaped receive instead of the promise-shaped one above).
33
- * Optional capabilities. */
34
- tryReceive?(): [Uint8Array, NetAddr] | undefined;
35
- receiveReady?(): boolean;
31
+ * poll-shaped receive instead of the promise-shaped one above). */
32
+ tryReceive(): [Uint8Array, NetAddr] | undefined;
33
+ receiveReady(): boolean;
36
34
  /** The CURRENT epoch's wake promise (promise-swap: settles when a
37
35
  * datagram arrives, the socket errors, or it closes; re-armed per event). */
38
- waitReceive?(): Promise<void>;
36
+ waitReceive(): Promise<void>;
39
37
  }
40
38
  export type ListenDatagram = (options: {
41
39
  transport: "udp";
@@ -51,9 +49,8 @@ export interface TcpConn {
51
49
  write(p: Uint8Array): Promise<number>;
52
50
  closeWrite(): Promise<void>;
53
51
  close(): void;
54
- /** SO_KEEPALIVE + TCP_KEEPIDLE (node exposes exactly this pair).
55
- * Optional capability: absent = the provider answers `not-supported`. */
56
- setKeepAlive?(enabled: boolean, idleMs: number): void;
52
+ /** SO_KEEPALIVE + TCP_KEEPIDLE (node exposes exactly this pair). */
53
+ setKeepAlive(enabled: boolean, idleMs: number): void;
57
54
  }
58
55
  export type TcpConnect = (options: {
59
56
  transport: "tcp";
@@ -73,11 +70,11 @@ export interface TcpListener {
73
70
  settled(): Promise<void>;
74
71
  accept(): Promise<TcpConn>;
75
72
  close(): void;
76
- /** Non-blocking accept + readiness (the 0.2 poll-shaped accept).
77
- * Optional capabilities; same promise-swap contract as `waitReceive`. */
78
- tryAccept?(): TcpConn | undefined;
79
- acceptReady?(): boolean;
80
- waitAccept?(): Promise<void>;
73
+ /** Non-blocking accept + readiness (the 0.2 poll-shaped accept);
74
+ * same promise-swap contract as `waitReceive`. */
75
+ tryAccept(): TcpConn | undefined;
76
+ acceptReady(): boolean;
77
+ waitAccept(): Promise<void>;
81
78
  }
82
79
  export type TcpListen = (options: {
83
80
  transport: "tcp";
@@ -26,7 +26,7 @@ export interface Ipv6SocketAddress {
26
26
  address: Ipv6Address;
27
27
  scopeId: number;
28
28
  }
29
- /** The `ip-socket-address` variant, in `{ kind, value }` form (A10). */
29
+ /** The `ip-socket-address` variant, in `{ kind, value }` form (embedder-api.md §"Naming and casing"). */
30
30
  export type IpSocketAddress = {
31
31
  kind: "ipv4";
32
32
  value: Ipv4SocketAddress;
@@ -222,7 +222,8 @@ export type TcpSendSource = Stream<number> | AsyncIterable<Uint8Array | number[]
222
222
  export type TcpByteStream = AsyncIterable<Uint8Array> | Iterable<Uint8Array>;
223
223
  /**
224
224
  * What tcp `listen` returns: the perpetual accept stream. `cancel` is the
225
- * A13 producer-cancellation hook the runtime's pump invokes when the
225
+ * producer-cancellation hook (embedder-api.md §"Streams and futures")
226
+ * the runtime's pump invokes when the
226
227
  * guest drops the stream while the loop is parked in accept(); direct
227
228
  * (non-runtime) consumers may call it themselves to stop accepting.
228
229
  */
@@ -233,13 +234,13 @@ export type TcpAcceptStream = AsyncIterable<TcpSocket> & {
233
234
  * The host-implemented `tcp-socket` resource surface (client + listener
234
235
  * halves — module header). `send` is a WIT sync func returning
235
236
  * `future<result>`: the async method's promise is lowered as the future
236
- * source (amendment A12), so the guest's call returns immediately and the
237
- * future settles when transmission completes. `receive`'s tuple carries
238
- * the byte stream and the future that reports FIN (`ok`) vs abnormal
239
- * close (`err`). `listen` returns the perpetual accept stream — an
240
- * async iterable of connected `TcpSocket` resources, lowered as
241
- * `stream<own<tcp-socket>>` (amendment A13: elements the guest never
242
- * takes are destroyed, closing their connections). Dropping the guest
237
+ * source (embedder-api.md §"Streams and futures"), so the guest's call
238
+ * returns immediately and the future settles when transmission completes.
239
+ * `receive`'s tuple carries the byte stream and the future that reports
240
+ * FIN (`ok`) vs abnormal close (`err`). `listen` returns the perpetual
241
+ * accept stream — an async iterable of connected `TcpSocket` resources,
242
+ * lowered as `stream<own<tcp-socket>>` (§"Streams and futures": elements
243
+ * the guest never takes are destroyed, closing their connections). Dropping the guest
243
244
  * handle does NOT close a socket with live pumps or a live accept stream
244
245
  * (the WIT's shared-ownership note); the OS socket closes when the
245
246
  * handle and every derived stream are all retired.
package/types/io.d.ts CHANGED
@@ -26,8 +26,9 @@ export declare class IoError {
26
26
  * A pollable over host-supplied readiness.
27
27
  *
28
28
  * WIT-facing surface: `ready()` and `block()` (the latter parks the
29
- * calling wasm frame when unready — @suspending, embedder-api.md A2:
30
- * the class prototype is the brand authority).
29
+ * calling wasm frame when unready — @suspending, embedder-api.md
30
+ * §"Functions and async": the class prototype is the brand authority
31
+ * ("prototype declares, instances behave").
31
32
  *
32
33
  * Host-facing surface: the constructor and `waitPromise()`. `ready` must
33
34
  * be cheap and side-effect-free; `wait` returns a promise that settles
@@ -86,10 +87,10 @@ export declare class InputStream {
86
87
  #private;
87
88
  constructor(buf?: Uint8Array);
88
89
  read(len: bigint): Uint8Array;
89
- /** Park-capable (A14): the buffer-backed base never parks. */
90
+ /** Park-capable: the buffer-backed base never parks. */
90
91
  blockingRead(len: bigint): Uint8Array | Promise<Uint8Array>;
91
92
  skip(len: bigint): bigint;
92
- /** Park-capable (A14): the buffer-backed base never parks. */
93
+ /** Park-capable: the buffer-backed base never parks. */
93
94
  blockingSkip(len: bigint): bigint | Promise<bigint>;
94
95
  subscribe(): Pollable;
95
96
  [Symbol.dispose](): void;
@@ -105,17 +106,17 @@ export declare class OutputStream {
105
106
  constructor(sink: (chunk: Uint8Array) => void);
106
107
  checkWrite(): bigint;
107
108
  write(contents: Uint8Array): void;
108
- /** Park-capable (A14): the never-backpressured base never parks. */
109
+ /** Park-capable: the never-backpressured base never parks. */
109
110
  blockingWriteAndFlush(contents: Uint8Array): void | Promise<void>;
110
111
  flush(): void;
111
- /** Park-capable (A14): the never-backpressured base never parks. */
112
+ /** Park-capable: the never-backpressured base never parks. */
112
113
  blockingFlush(): void | Promise<void>;
113
114
  subscribe(): Pollable;
114
115
  writeZeroes(len: bigint): void;
115
- /** Park-capable (A14): the never-backpressured base never parks. */
116
+ /** Park-capable: the never-backpressured base never parks. */
116
117
  blockingWriteZeroesAndFlush(len: bigint): void | Promise<void>;
117
118
  splice(src: InputStream, len: bigint): bigint;
118
- /** Park-capable (A14): the never-backpressured base never parks. */
119
+ /** Park-capable: the never-backpressured base never parks. */
119
120
  blockingSplice(src: InputStream, len: bigint): bigint | Promise<bigint>;
120
121
  [Symbol.dispose](): void;
121
122
  }
@@ -133,7 +134,7 @@ export type ByteSink = (chunk: Uint8Array) => void | Promise<void>;
133
134
  * generic bridge from any `AsyncIterable<Uint8Array>` (host stdin, an
134
135
  * OPFS file read) to p2 stream semantics. `read` on an empty open stream
135
136
  * returns an empty list (p2's non-blocking contract), `blocking-read`
136
- * parks until bytes or EOF (A14/A2 mark relay — duck-typed against the
137
+ * parks until bytes or EOF (mark relay — duck-typed against the
137
138
  * registered `InputStream`, the marks relay from that prototype), and
138
139
  * EOF-with-drained-buffer is the `closed` stream-error. The feed pauses
139
140
  * past the high-water mark (no unbounded buffering).
@@ -147,7 +148,7 @@ export declare class FedInputStream {
147
148
  #private;
148
149
  constructor(source: AsyncIterable<Uint8Array>, highWater?: number);
149
150
  read(len: bigint): Uint8Array;
150
- /** Parks (A14/A2 mark relay from the registered prototype). */
151
+ /** Parks (mark relay from the registered prototype). */
151
152
  blockingRead(len: bigint): Uint8Array | Promise<Uint8Array>;
152
153
  skip(len: bigint): bigint;
153
154
  blockingSkip(len: bigint): bigint | Promise<bigint>;
@@ -159,7 +160,7 @@ export declare class FedInputStream {
159
160
  * budget: `check-write` reports the remaining permit (writing past it is
160
161
  * the guest's contract violation and traps via unbranded throw),
161
162
  * `blocking-flush`/`blocking-write-and-flush` park until the sink
162
- * drained everything (A14/A2 mark relay), `subscribe` wakes when budget
163
+ * drained everything (mark relay), `subscribe` wakes when budget
163
164
  * frees. A sink failure surfaces as the `last-operation-failed`
164
165
  * stream-error carrying an `IoError`.
165
166
  *
@@ -173,7 +174,7 @@ export declare class SinkOutputStream {
173
174
  checkWrite(): bigint;
174
175
  write(contents: Uint8Array): void;
175
176
  flush(): void;
176
- /** Parks until the sink drained everything (A14/A2 mark relay). */
177
+ /** Parks until the sink drained everything (mark relay). */
177
178
  blockingFlush(): void | Promise<void>;
178
179
  /** Parks until this write (and everything before it) drained. */
179
180
  blockingWriteAndFlush(contents: Uint8Array): void | Promise<void>;