@polyengine/runtime 0.5.0 → 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.
Files changed (82) hide show
  1. package/esm/cabi/async_values.js +6 -5
  2. package/esm/cabi/bulk_lists.js +0 -5
  3. package/esm/cabi/context.js +13 -3
  4. package/esm/cabi/flatten.js +41 -9
  5. package/esm/cabi/handles.js +57 -54
  6. package/esm/cabi/layout.js +113 -52
  7. package/esm/cabi/load.js +31 -23
  8. package/esm/cabi/store.js +33 -26
  9. package/esm/cabi/trap.js +2 -2
  10. package/esm/cabi/types.js +138 -25
  11. package/esm/cabi/values.js +25 -7
  12. package/esm/cache/core.js +2 -11
  13. package/esm/digest/digest.js +10 -8
  14. package/esm/digest/mod.js +1 -1
  15. package/esm/digest/verify.js +6 -86
  16. package/esm/embedder/casing.js +24 -9
  17. package/esm/embedder/copy.js +6 -6
  18. package/esm/embedder/errors.js +2 -2
  19. package/esm/embedder/imports.js +3 -3
  20. package/esm/embedder/instantiate.js +170 -41
  21. package/esm/embedder/mod.js +9 -8
  22. package/esm/embedder/resources.js +39 -16
  23. package/esm/embedder/streams.js +36 -37
  24. package/esm/embedder/sync.js +242 -0
  25. package/esm/embedder/values.js +84 -22
  26. package/esm/embedder/version.js +9 -9
  27. package/esm/exec/boundary.js +318 -165
  28. package/esm/exec/executor.js +51 -24
  29. package/esm/exec/host_streams.js +31 -31
  30. package/esm/intrinsics/async_builtins.js +19 -8
  31. package/esm/intrinsics/context.js +1 -1
  32. package/esm/intrinsics/errors.js +9 -9
  33. package/esm/intrinsics/fact_calls.js +37 -49
  34. package/esm/intrinsics/mod.js +54 -117
  35. package/esm/intrinsics/stream_builtins.js +2 -2
  36. package/esm/intrinsics/transcode.js +1 -1
  37. package/esm/jspi/bridge.js +4 -3
  38. package/esm/jspi/suspending.js +10 -5
  39. package/esm/plan/loader.js +5 -5
  40. package/esm/shim/translator.js +2 -2
  41. package/esm/task/mod.js +45 -182
  42. package/esm/task/scheduler.js +154 -185
  43. package/esm/task/streams.js +39 -54
  44. package/esm/task/subtask.js +2 -2
  45. package/esm/task/thread.js +20 -41
  46. package/esm/task/waitable.js +0 -1
  47. package/package.json +2 -2
  48. package/types/cabi/async_values.d.ts +3 -2
  49. package/types/cabi/bulk_lists.d.ts +0 -2
  50. package/types/cabi/context.d.ts +15 -5
  51. package/types/cabi/flatten.d.ts +2 -2
  52. package/types/cabi/handles.d.ts +15 -26
  53. package/types/cabi/layout.d.ts +22 -1
  54. package/types/cabi/load.d.ts +10 -2
  55. package/types/cabi/store.d.ts +4 -2
  56. package/types/cabi/types.d.ts +22 -3
  57. package/types/digest/mod.d.ts +1 -1
  58. package/types/digest/verify.d.ts +3 -19
  59. package/types/embedder/casing.d.ts +9 -1
  60. package/types/embedder/copy.d.ts +4 -4
  61. package/types/embedder/instantiate.d.ts +4 -4
  62. package/types/embedder/mod.d.ts +3 -2
  63. package/types/embedder/resources.d.ts +20 -7
  64. package/types/embedder/streams.d.ts +5 -6
  65. package/types/embedder/sync.d.ts +81 -0
  66. package/types/embedder/values.d.ts +2 -2
  67. package/types/exec/boundary.d.ts +66 -42
  68. package/types/exec/executor.d.ts +3 -2
  69. package/types/exec/host_streams.d.ts +8 -8
  70. package/types/intrinsics/errors.d.ts +3 -3
  71. package/types/intrinsics/mod.d.ts +1 -1
  72. package/types/intrinsics/stream_builtins.d.ts +2 -2
  73. package/types/jspi/bridge.d.ts +6 -5
  74. package/types/jspi/suspending.d.ts +1 -1
  75. package/types/plan/format.d.ts +11 -10
  76. package/types/plan/loader.d.ts +2 -2
  77. package/types/shim/translator.d.ts +2 -2
  78. package/types/task/mod.d.ts +26 -97
  79. package/types/task/scheduler.d.ts +81 -62
  80. package/types/task/streams.d.ts +23 -38
  81. package/types/task/subtask.d.ts +2 -2
  82. package/types/task/waitable.d.ts +0 -1
@@ -3,17 +3,11 @@ import type { WirePlan } from "../plan/format.js";
3
3
  export interface DigestMismatch {
4
4
  expected: string;
5
5
  actual: string;
6
- /** Human-readable description of the first structural divergence found,
7
- * or `null` if a divergence exists but no finer-grained cause could be
8
- * isolated (e.g. the two canonical JSON trees have the same top-level
9
- * shape but the digest still differs — should not happen in practice
10
- * since the digest is a pure hash of that JSON, but guarded anyway). */
11
- firstDivergence: string | null;
12
6
  }
13
7
  /**
14
8
  * Thrown by generated `instantiate` wrappers when the loaded plan's world
15
9
  * digest does not match the constant bindgen embedded at generation time
16
- * (contracts/digest.md: "fails fast with a structural diff on mismatch").
10
+ * (contracts/digest.md: "fails fast on mismatch").
17
11
  * Raised BEFORE the component is instantiated, so no guest code has run
18
12
  * when a caller catches this.
19
13
  *
@@ -25,7 +19,7 @@ export declare class WorldDigestMismatchError extends Error {
25
19
  readonly name = "WorldDigestMismatchError";
26
20
  /** The world these bindings were generated from. */
27
21
  readonly world: string;
28
- /** The full mismatch report (expected/actual digest, first divergence). */
22
+ /** The full mismatch report (expected/actual digest). */
29
23
  readonly mismatch: DigestMismatch;
30
24
  constructor(world: string, mismatch: DigestMismatch);
31
25
  /** Digest the generated bindings were built against. */
@@ -36,17 +30,7 @@ export declare class WorldDigestMismatchError extends Error {
36
30
  /**
37
31
  * Verify `plan`'s computed world digest against `expectedDigest` (the
38
32
  * constant bindgen embedded at generation time). Returns `null` on match,
39
- * or a `DigestMismatch` report naming the first divergent path on
40
- * mismatch.
33
+ * or a `DigestMismatch` report on mismatch.
41
34
  * @internal
42
35
  */
43
36
  export declare function verifyWorldDigest(plan: WirePlan, expectedDigest: string): Promise<DigestMismatch | null>;
44
- /**
45
- * `diffWorldDigest` — richer variant for tests/tooling: compare against
46
- * another plan's (or a WIT-derived) canonical JSON directly, walking both
47
- * trees in parallel to name the first divergent import/export/type path.
48
- * `expectedCanonicalJson` is normally produced by `crates/bindgen`'s
49
- * `digest --json` output, or by `computeWorldDigest` on a reference plan.
50
- * @internal
51
- */
52
- export declare function diffWorldDigest(plan: WirePlan, expectedCanonicalJson: string): Promise<DigestMismatch | null>;
@@ -25,7 +25,7 @@ export declare function pascalCase(label: string): string;
25
25
  *
26
26
  * The Component Model mangles resource membership into the name itself
27
27
  * (`[constructor]counter`, `[method]counter.increment`,
28
- * `[static]counter.merge`); C0 finding #2 was embedders hand-transcribing
28
+ * `[static]counter.merge`); embedders would otherwise hand-transcribe
29
29
  * these. Assembling and disassembling them is a runtime obligation.
30
30
  */
31
31
  export type LeafName = {
@@ -45,6 +45,14 @@ export type LeafName = {
45
45
  };
46
46
  /**
47
47
  * Decode a mangled leaf name; unmangled names come back as `plain`.
48
+ *
49
+ * An unknown bracket form throws rather than falling back to `plain`
50
+ * (contracts/embedder-api.md §"Getters and setters (pre-ruling…)", final
51
+ * paragraph): "the runtime refuses unknown bracket forms in mangled names
52
+ * loudly at instantiation (rather than misbinding them as plain names — a
53
+ * `[get]foo` treated as a function named `[get]foo` would be wrong in both
54
+ * directions)". Getter/setter support (`[get]`/`[set]`, upstream
55
+ * WebAssembly/component-model#701) is tracked in polyengine#254.
48
56
  * @internal — leaf-name demangling, performed by the runtime and by
49
57
  * bindgen-generated code.
50
58
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The URL of this copy of the runtime. Identity of the copy.
3
- * @internal — copy-identity constant for the A9 multi-copy diagnostics; not
3
+ * @internal — copy-identity constant for the module identity multi-copy diagnostics; not
4
4
  * host-facing.
5
5
  */
6
6
  export declare const COPY_URL: string;
@@ -14,15 +14,15 @@ export declare const COPY_URL: string;
14
14
  *
15
15
  * INVARIANT: keep in sync with `version` in runtime/deno.json — pinned by
16
16
  * runtime/tests/embedder/cross_copy_test.ts.
17
- * @internal — copy-identity constant for the A9 multi-copy diagnostics; not
17
+ * @internal — copy-identity constant for the module identity multi-copy diagnostics; not
18
18
  * host-facing.
19
19
  */
20
- export declare const RUNTIME_VERSION = "0.5.0";
20
+ export declare const RUNTIME_VERSION = "0.6.0";
21
21
  /**
22
22
  * Compose a cross-copy diagnostic: what was foreign, which copy is speaking,
23
23
  * the census of every copy in the graph, and the by-value remediation.
24
24
  *
25
- * Kept to one line but complete — the whole point of A9's stateful half is
25
+ * Kept to one line but complete — the whole point of module identity's stateful half is
26
26
  * that "recognized but foreign" is a NAMED failure, never a silent
27
27
  * adaptation (a foreign `Stream` pumped as an async iterable) and never a
28
28
  * misleading generic ("handle is not an error-context").
@@ -10,7 +10,7 @@ export interface ComponentArtifacts {
10
10
  }
11
11
  /**
12
12
  * Untranslated alternative to `ComponentArtifacts` (embedder-api.md
13
- * amendment A3): hand `instantiate` the raw component plus the translator
13
+ * §"Module wiring and instantiation"): hand `instantiate` the raw component plus the translator
14
14
  * and it runs the translation internally — the pipeline collapses to
15
15
  * bytes-in, instance-out.
16
16
  *
@@ -33,7 +33,7 @@ export type InstantiateSource = ComponentArtifacts | UntranslatedArtifacts;
33
33
  * single-file JSON emitted by build-time translation (`tools/translate`,
34
34
  * or `Translator.translateRaw`), carrying the plan and the FACT adapter
35
35
  * modules. The production deploy set is `component.wasm` + its envelope +
36
- * the runtime: no translator ships (embedder-api.md amendment A4).
36
+ * the runtime: no translator ships (contracts/embedder-api.md §"Module wiring and instantiation").
37
37
  *
38
38
  * Pure and fetch-agnostic: acquire the two blobs however the platform
39
39
  * likes (HTTP, fs, bundler asset) and hand them over. The envelope embeds
@@ -48,7 +48,7 @@ export declare function artifactsFromEnvelope(envelopeJson: string, componentByt
48
48
  * guest code runs: generated `instantiate` wrappers call this, verify the
49
49
  * plan, and only then delegate to `instantiate` below.
50
50
  * @internal — bindgen-generated code only — the digest handshake needs the
51
- * plan before instantiating (amendment A17).
51
+ * plan before instantiating (§"Module wiring and instantiation").
52
52
  */
53
53
  export declare function resolveArtifacts(src: InstantiateSource): Promise<ComponentArtifacts>;
54
54
  export interface EmbedderOptions {
@@ -80,7 +80,7 @@ export interface EmbedderInstance {
80
80
  */
81
81
  export declare function instantiate(source: InstantiateSource, imports?: Record<string, unknown>, opts?: EmbedderOptions): Promise<EmbedderInstance>;
82
82
  /**
83
- * Alias matching the C2 dispatch's spelling.
83
+ * Alias matching bindgen's generated import spelling.
84
84
  * @internal — alias kept for bindgen-generated code only; hosts call
85
85
  * `instantiate`.
86
86
  */
@@ -9,6 +9,7 @@ export declare function createStream<T>(): {
9
9
  writer: ProtocolStreamWriter<T>;
10
10
  };
11
11
  export { GuestResource, HostResourceRegistry } from "./resources.js";
12
- export { camelCase, type LeafName, parseLeafName, pascalCase } from "./casing.js";
13
- export { asTrackKeySpelling, compareSemver, ImportRegistrationError, ImportResolutionError, ImportResolver, type ParsedId, parseInterfaceId, parseSemver, type Semver, trackKey, } from "./version.js";
12
+ export { camelCase, pascalCase } from "./casing.js";
13
+ export { ImportRegistrationError, ImportResolutionError, ImportResolver, } from "./version.js";
14
14
  export { type AdapterOptions, BorrowScope, fromHost, toHost, type ValueBridge, } from "./values.js";
15
+ export { type Sync, sync } from "./sync.js";
@@ -2,17 +2,17 @@ import type { ResourceTypeInfo, ValType } from "../cabi/types.js";
2
2
  /**
3
3
  * Internal state of a guest-resource wrapper.
4
4
  *
5
- * The KEY is the process-global `polyengine.resourceState/1` brand since amendment
6
- * A9 (it used to be a module-local `Symbol(...)`, on the now-repealed
5
+ * The KEY is the process-global `polyengine.resourceState/1` brand
6
+ * (contracts/embedder-api.md §"Module identity": it used to be a module-local `Symbol(...)`, on the now-repealed
7
7
  * assumption that bundle and source runtimes are never mixed in one process —
8
8
  * issue #83 showed they routinely are). The state SHAPE stays strictly
9
- * runtime-internal, exactly as the A9 brand table notes: another copy may
9
+ * runtime-internal, exactly as the module identity brand table notes: another copy may
10
10
  * RECOGNIZE a wrapper, and must never read or write this object. `copyUrl` is
11
11
  * what lets this copy tell its own wrappers from a foreign copy's.
12
12
  */
13
13
  declare const STATE: symbol;
14
14
  interface WrapperState {
15
- /** The runtime copy that minted this wrapper (A9). */
15
+ /** The runtime copy that minted this wrapper. */
16
16
  copyUrl: string;
17
17
  rep: number;
18
18
  /** False once the handle was transferred away or dropped. */
@@ -69,7 +69,7 @@ export declare function initWrapper(w: GuestResource, state: Omit<WrapperState,
69
69
  *
70
70
  * A wrapper minted by ANOTHER copy carries the same (process-global) brand key
71
71
  * but its state belongs to that copy — reading it here would be reading a
72
- * foreign copy's private shape (A9). So it is not a state: it is
72
+ * foreign copy's private shape. So it is not a state: it is
73
73
  * `undefined` here, and `requireLive` turns that into the named cross-copy
74
74
  * error rather than a misleading "not a resource handle" / "not live".
75
75
  */
@@ -100,15 +100,28 @@ export interface GuestResourceSpec {
100
100
  raw: (...a: unknown[]) => unknown;
101
101
  params: ValType[];
102
102
  results: ValType[];
103
+ /** True for an `async func` — see sync()'s `{ kind: "async" }` brand. */
104
+ async: boolean;
103
105
  }[];
104
106
  statics: {
105
107
  member: string;
106
108
  raw: (...a: unknown[]) => unknown;
107
109
  params: ValType[];
108
110
  results: ValType[];
111
+ async: boolean;
109
112
  }[];
110
113
  }
111
- export type CallAdapter = (raw: (...a: unknown[]) => unknown, params: ValType[], results: ValType[], where: string, args: unknown[]) => Promise<unknown>;
114
+ /**
115
+ * Build (once, at class-build time — never per call) the Promise-shaped
116
+ * wrapper for one method/static's raw lifted function, exactly as
117
+ * `Facade#wrapExportFn` would for a plain export. `buildGuestResourceClass`
118
+ * reads the sync() brand off the returned wrapper to install the matching
119
+ * `"method"`/`"free"`/`"async"` brand on the class member it builds around
120
+ * it — the wrapper itself IS what a per-call closure invokes, so a `self`
121
+ * receiver is `wrapper(self, ...args)` for a method the same way a bare
122
+ * export is `wrapper(...args)`.
123
+ */
124
+ export type ExportWrapper = (raw: (...a: unknown[]) => unknown, params: ValType[], results: ValType[], async: boolean, where: string) => (...args: unknown[]) => Promise<unknown>;
112
125
  /**
113
126
  * Build the class for a guest-implemented resource.
114
127
  *
@@ -118,7 +131,7 @@ export type CallAdapter = (raw: (...a: unknown[]) => unknown, params: ValType[],
118
131
  * synchronously is reported as such rather than silently returning a
119
132
  * half-built object (see the report's contract-friction list).
120
133
  */
121
- export declare function buildGuestResourceClass(spec: GuestResourceSpec, rt: ResourceTypeInfo, adapt: CallAdapter, lowerArgs: (args: unknown[], params: ValType[], where: string) => unknown[]): any;
134
+ export declare function buildGuestResourceClass(spec: GuestResourceSpec, rt: ResourceTypeInfo, wrapExport: ExportWrapper, lowerArgs: (args: unknown[], params: ValType[], where: string) => unknown[]): any;
122
135
  /** Materialize an `own`/`borrow` wrapper for a rep coming out of a guest. */
123
136
  export declare function makeWrapper(cls: any, rep: number, rt: ResourceTypeInfo, owns: boolean): GuestResource;
124
137
  /**
@@ -15,7 +15,7 @@ export interface ElemCodec<T> {
15
15
  /** conventions value -> internal component value */
16
16
  fromHost(v: T): ComponentValue;
17
17
  /**
18
- * Destroy a LOWERED element the reader will never take (amendment A13);
18
+ * Destroy a LOWERED element the reader will never take (§"Streams and futures");
19
19
  * present only for element types that hold resources (`own<R>`), where
20
20
  * abandonment without destruction is a leak.
21
21
  */
@@ -69,7 +69,7 @@ export declare class Stream<T> implements ProtocolStream<T> {
69
69
  read(max: number): Promise<Chunk<T>>;
70
70
  /**
71
71
  * Consume the writer's bytes in place, without an intermediate chunk
72
- * (`stream<u8>` only — contracts/embedder-api.md amendment A21,
72
+ * (`stream<u8>` only — contracts/embedder-api.md §"Streams and futures" ("Direct-access byte edges"),
73
73
  * polyengine#128).
74
74
  *
75
75
  * At every rendezvous with a writer of nonzero capacity, `consume` runs
@@ -86,7 +86,7 @@ export declare class Stream<T> implements ProtocolStream<T> {
86
86
  * writer's parked operation survives and the stream stays alive.
87
87
  *
88
88
  * Refusals mirror `read`: an unbound `Stream.create()` handle and a handle
89
- * already passed to a guest (the A15 transfer guard) both throw, as does a
89
+ * already passed to a guest (the deadlock-verdict suppression transfer guard) both throw, as does a
90
90
  * non-`u8` element type.
91
91
  */
92
92
  readDirect(consume: (src: DirectSource) => DirectVerdict): Promise<number>;
@@ -113,7 +113,7 @@ export declare class Stream<T> implements ProtocolStream<T> {
113
113
  * DROPPED event into the trapping instance's waitables, and a later
114
114
  * driving loop asserted on the corpse).
115
115
  *
116
- * The arm is released on this path too (#162, amendment A15): the wrapper's
116
+ * The arm is released on this path too (#162, §"Streams and futures"): the wrapper's
117
117
  * `HostActivity` now closes through the shared object's drop observers,
118
118
  * which `dropSharedForTeardown` fires unconditionally — so a teardown with
119
119
  * nothing parked no longer leaves the arm outliving the stream. (This
@@ -143,7 +143,7 @@ export declare class StreamWriter<T> implements ProtocolStreamWriter<T> {
143
143
  write(values: Chunk<T>): Promise<number>;
144
144
  /**
145
145
  * Fill the reader's landing zone in place, without an intermediate chunk
146
- * (`stream<u8>` only — contracts/embedder-api.md amendment A21,
146
+ * (`stream<u8>` only — contracts/embedder-api.md §"Streams and futures" ("Direct-access byte edges"),
147
147
  * polyengine#128).
148
148
  *
149
149
  * At every rendezvous with a reader of nonzero capacity, `produce` runs
@@ -197,7 +197,6 @@ export declare class Future<T> implements ProtocolFuture<T> {
197
197
  * is itself PromiseLike, so `await` still works and still yields `T`), which
198
198
  * keeps `drop()`/`cancel()` reachable for a caller that does not await. The
199
199
  * alternative — resolving a Promise *to* the handle — is not expressible.
200
- * Flagged in the C2 report.
201
200
  */
202
201
  static deferred<T>(pending: Promise<ComponentValue>, codec: ElemCodec<T>): Future<T>;
203
202
  /** @internal */
@@ -0,0 +1,81 @@
1
+ /** The registry symbol. `Symbol.for` per module identity: N runtime copies agree on it
2
+ * without sharing modules. */
3
+ export declare const SYNC_CALLABLE: unique symbol;
4
+ /**
5
+ * The brand payload, keyed by what the branded value is.
6
+ *
7
+ * - `"free"` — a lifted export function (plain export, interface member, or
8
+ * resource static): `fn` is the fully-wrapped synchronous form.
9
+ * - `"method"` — a guest-resource prototype method: `fn` takes the resource
10
+ * instance as its first argument (the `borrow<R>`/`own<R>` self param the
11
+ * lifted function already declares).
12
+ * - `"async"` — an async-typed export: carries no synchronous form, named so
13
+ * `sync()` can report the real reason.
14
+ */
15
+ export type SyncPayload = {
16
+ kind: "free";
17
+ fn: (...args: unknown[]) => unknown;
18
+ } | {
19
+ kind: "method";
20
+ fn: (self: unknown, ...args: unknown[]) => unknown;
21
+ } | {
22
+ kind: "async";
23
+ };
24
+ /**
25
+ * Stamp `payload` on `target` under the brand: non-enumerable, non-writable,
26
+ * matching `@polyengine/protocol`'s `defineBrand` (protocol/src/brands.ts) —
27
+ * implemented locally since the runtime does not add application-tier
28
+ * vocabulary to the protocol package.
29
+ *
30
+ * @internal — written by `instantiate.ts` and `resources.ts` at wrap/
31
+ * class-build time; not part of the public `sync()` surface.
32
+ */
33
+ export declare function markSyncCallable(target: object, payload: SyncPayload): void;
34
+ /**
35
+ * Read the brand payload off `target`, or `undefined` if unbranded.
36
+ * Structural, like `hasBrand`: accepts a payload minted by any copy.
37
+ * @internal
38
+ */
39
+ export declare function syncPayloadOf(target: unknown): SyncPayload | undefined;
40
+ /** `Promise<R>`-returning functions synchronize to `R`; records map
41
+ * recursively; everything else passes through. Type-level refusal of an
42
+ * async export is not attempted (the contract only requires the runtime
43
+ * error) — `Sync<F>` stays structural.
44
+ *
45
+ * CONTRACT: the naive `F extends Record<string, unknown>` branch (checked
46
+ * before this fix) only matches object-LITERAL type aliases — named
47
+ * interfaces (generated `*Exports`) and class instance types (generated
48
+ * resource classes, e.g. `Counter`) have no implicit index signature and
49
+ * are not assignable to it, so they fell through to the `: F` passthrough
50
+ * and stayed Promise-shaped. Ordering matters: a non-Promise function type
51
+ * (e.g. a resource's `drop(): void`, or `[Symbol.dispose]`) must be checked
52
+ * and passed through BEFORE the generic `object` branch, or `{ [K in keyof
53
+ * F]: ... }` would try to map over a function's call signature (losing it)
54
+ * instead of leaving the function itself alone. */
55
+ export type Sync<F> = F extends (...a: infer A) => Promise<infer R> ? (...a: A) => R : F extends (...a: never[]) => unknown ? F : F extends object ? {
56
+ [K in keyof F]: Sync<F[K]>;
57
+ } : F;
58
+ /**
59
+ * The synchronous form of a WIT-sync export (contracts/embedder-api.md
60
+ * §"Functions and async", §"Functions and async").
61
+ *
62
+ * - `sync(fn)` — a lifted export function (plain export, interface member,
63
+ * or resource static): returns the synchronous form `(...args) => T`.
64
+ * - `sync(instance)` — a guest-resource wrapper: a view whose members call
65
+ * the synchronous forms with `instance` as receiver.
66
+ * - `sync(cls)` — a guest-resource class: a view of synchronous statics
67
+ * (constructors are already synchronous; `new` the class itself).
68
+ * - `sync(record)` — an exports record or nested interface record: a view
69
+ * with every member mapped by these same rules, recursively; non-branded
70
+ * members pass through unchanged.
71
+ * - Views are stable: `sync(x) === sync(x)`.
72
+ * - An async-typed export, a bare resource-method function, or anything
73
+ * unbranded throws `TypeError`.
74
+ */
75
+ export declare function sync<F extends (...a: never[]) => Promise<unknown>>(target: F): Sync<F>;
76
+ export declare function sync<T extends object>(target: T): Sync<T>;
77
+ /** Fallback for a non-branded/primitive target — always throws at runtime
78
+ * (see the dispatch above); typed loosely so a caller passing an arbitrary
79
+ * value (as opposed to a known export/record/instance/class shape) still
80
+ * type-checks, matching the runtime's willingness to name the mistake. */
81
+ export declare function sync(target: unknown): unknown;
@@ -23,8 +23,8 @@ export interface ValueBridge {
23
23
  kind: "borrow";
24
24
  }): number;
25
25
  /**
26
- * Destroy a LOWERED `own<R>` the guest will never receive (amendment
27
- * A13: a stream element the producer lowered but the reader never took).
26
+ * Destroy a LOWERED `own<R>` the guest will never receive (a resource
27
+ * stream element the producer lowered but the reader never took).
28
28
  * Runs the resource's destructor — for a host-implemented R the
29
29
  * instance's `[Symbol.dispose]`, for a guest-implemented R the guest
30
30
  * dtor — exactly as if the guest had taken the handle and dropped it.
@@ -114,7 +114,7 @@ export declare function normalizeCoreValues(values: CoreValue[], lanes: CoreType
114
114
  * repeat. Reimplementing it there diverged: that copy only drained
115
115
  * `store.awaiting` and never awaited `pendingHostCalls`, so a guest parked on
116
116
  * a Promise-returning host import was never resumed and the host's read of
117
- * the stream it was feeding hung (C0 finding R-1).
117
+ * the stream it was feeding hung (host-pump starvation of `pendingHostCalls`).
118
118
  *
119
119
  * Callers that must not hit the deadlock traps below (the host pump: an
120
120
  * embedder that never does its half is documented to hang, not trap) can
@@ -146,26 +146,35 @@ export declare function ensureSettlementPump(store: Store): void;
146
146
  * needs genuine wasm-frame suspension: `needsJspi`, at the precise point.
147
147
  */
148
148
  /**
149
- * The plain-entered variant of a `[constructor]` export in jspi mode,
150
- * attached to the promising-wrapped lifted function under this symbol.
149
+ * The plain-entered variant of a **sync-typed** lifted export in jspi mode,
150
+ * attached to the promising-wrapped lifted function under this symbol
151
+ * (contracts/embedder-api.md §"Functions and async", §"Functions and async").
151
152
  *
152
- * A WIT constructor is surfaced as a JS class constructor
153
- * (contracts/embedder-api.md §"Resources"), and a JS constructor cannot
154
- * await but in jspi mode every promising-wrapped entry returns a Promise
155
- * even when the activation completes without suspending (jspi pin (e)). So
156
- * constructor exports carry a second lifted function whose ENTRY is plain
157
- * (unwrapped): a constructor that completes synchronously the
158
- * overwhelmingly common case; WIT constructors are always sync-typed —
159
- * returns its rep synchronously through it.
153
+ * In jspi mode every promising-wrapped entry returns a Promise even when the
154
+ * activation completes without suspending (jspi pin (e)). Some host contexts
155
+ * cannot use a Promise no matter how promptly it resolves, so each sync-typed
156
+ * export carries a second lifted function whose ENTRY is plain (unwrapped):
157
+ * a guest activation that completes synchronously the overwhelmingly common
158
+ * case for sync-typed WIT delivers its results synchronously through it.
160
159
  *
161
- * The cost is confined to genuinely-suspending constructors, which no JS
162
- * host can surface as `new` anyway: a blocking built-in reached through the
163
- * plain entry signals `NeedsJspi` (a capability error, instance left
164
- * enterable), and a Suspending-wrapped host import reached from the
165
- * unwrapped frame fails as a trap. Both name the constructor rather than
166
- * silently deadlocking.
160
+ * Two consumers, one mechanism:
161
+ *
162
+ * * **resource constructors** a WIT constructor is surfaced as a JS class
163
+ * constructor (§"Resources") and a JS constructor cannot await, so the
164
+ * embedder layer reads this symbol unconditionally for `[constructor]`
165
+ * exports;
166
+ * * **the embedder `sync()` adapter** — the explicit per-use
167
+ * synchronous view of any sync-typed export.
168
+ *
169
+ * The cost is confined to genuinely-suspending activations: a blocking
170
+ * built-in reached through the plain entry signals `NeedsJspi` (a capability
171
+ * error, instance left enterable), and a `Suspending`-wrapped host import
172
+ * reached from the unwrapped frame fails as a trap. Both name the export
173
+ * rather than silently deadlocking. A call made while the instance has
174
+ * hop-parked activations refuses with `SyncEntryBusy` before entering
175
+ * (`refuseOnEntryHops` below).
167
176
  */
168
- export declare const CONSTRUCTOR_SYNC_ENTRY: unique symbol;
177
+ export declare const SYNC_ENTRY: unique symbol;
169
178
  export declare function createLiftedFunction(input: {
170
179
  name: string;
171
180
  ft: FuncType;
@@ -212,6 +221,22 @@ export declare function createLiftedFunction(input: {
212
221
  * Promise, which it can never advance, and declares a bogus deadlock.
213
222
  */
214
223
  allowAsyncCompletion?: boolean;
224
+ /**
225
+ * Refuse — synchronously, before entering — a call made while the instance
226
+ * has HOP-parked activations, instead of deferring it (§"Functions and async",
227
+ * failure-ladder arm 2).
228
+ *
229
+ * Set for the `SYNC_ENTRY` variant, which is built with
230
+ * `suspensionMode: "plain"` inside a *jspi-mode* instantiation: the
231
+ * hop-quiescence gate below is keyed on this function's own mode and so is
232
+ * dead for that variant, yet the hazard it exists to prevent is the
233
+ * instance's, not the entry's — a hop-parked activation's pending lift
234
+ * reads memory a fresh guest turn would mutate (see the gate's comment).
235
+ * A synchronous caller cannot be deferred, so it refuses instead. The
236
+ * refusal is pre-enter, hence non-poisoning: nothing was entered, so there
237
+ * is nothing to poison — the same structural safety as `entryRefusal`.
238
+ */
239
+ refuseOnEntryHops?: boolean;
215
240
  }): (...args: ComponentValue[]) => unknown;
216
241
  /**
217
242
  * Build the host-callable entry for a resource destructor — a full canonical
@@ -224,27 +249,13 @@ export declare function createLiftedFunction(input: {
224
249
  * callee = inst.store.lift(dtor, ft, opts, rt.impl)
225
250
  * ```
226
251
  *
227
- * Before #160 the host-initiated path (embedder `drop()`, the GC backstop,
228
- * `dropOwn`) hand-rolled the bracket in cabi/handles.ts `callDtorGated`: a
229
- * bare call to the dtor with `enterFrom(null)` HELD across the returned
230
- * promise. Three defects followed from having no Task/Thread behind the
231
- * activation:
232
- *
233
- * - **#160 itself**: the held bracket left the impl instance non-enterable,
234
- * so `Store.tick`'s enterability filter (#155) could never resume a
235
- * suspension point belonging to the dtor's own activation. The completion
236
- * promise sat in `pendingHostCalls` looking like external work, and every
237
- * driver parked on it forever.
238
- * - it was the runtime's only `enterFrom(null)` bracket spanning an await —
239
- * the macro-scale reachability window of the #156 class, through which a
240
- * sibling instance looked non-enterable from the synthetic root.
241
- * - built-ins reached inside the dtor had no ambient task (`currentTask()`
242
- * → `PendingCapability`, or a foreign-task misattribution, the #24 class).
243
- *
244
- * Under the lift harness all three go away structurally: the activation has a
245
- * real `Task` + implicit `Thread`, the entry bracket is released when the
246
- * first segment parks (`leave()` before `drive`), and settled tails flow
247
- * through `serviceSettled` like any other lifted sync call.
252
+ * The host-initiated paths (embedder `drop()`, the GC backstop, `dropOwn`)
253
+ * route through this harness rather than calling the dtor bare, because the
254
+ * activation then has a real `Task` + implicit `Thread`: built-ins reached
255
+ * inside the dtor are well-attributed (a bare call leaves `currentTask()`
256
+ * with no ambient task — `PendingCapability`, or a foreign-task
257
+ * misattribution, the #24 class), and settled tails flow through
258
+ * `serviceSettled` like any other lifted sync call.
248
259
  *
249
260
  * The returned function takes the rep and returns either `undefined` (the
250
261
  * activation completed synchronously — the overwhelmingly common case) or a
@@ -256,7 +267,7 @@ export declare function createDtorEntry(input: {
256
267
  /**
257
268
  * The destructor's core function, unwrapped: `createLiftedFunction` applies
258
269
  * `enterWasm` itself per `suspensionMode`. `null` is the reference's
259
- * `rt.dtor or (lambda rep: [])` — the bracket still runs.
270
+ * `rt.dtor or (lambda rep: [])` — the lift still runs.
260
271
  */
261
272
  dtor: CoreFn | null;
262
273
  /** `rt.impl`, the implementing instance the lift enters. */
@@ -293,7 +304,7 @@ export declare function hostDtorCall(rt: ResourceTypeInfo, rep: number): void;
293
304
  * This is the whole of the jspi entry seam. In **plain** mode the entry is not
294
305
  * `promising`-wrapped, `callCore` returns core values, and this returns them
295
306
  * without yielding — no await, no Promise allocation, the identical
296
- * synchronous path M1 shipped. In **jspi** mode the entry *is* wrapped, so the
307
+ * synchronous path plain mode always used. In **jspi** mode the entry *is* wrapped, so the
297
308
  * call returns a Promise (jspi pin (e)) and we park the thread on it via the
298
309
  * `awaitValue` block request; the driving loop resumes us with the values, or
299
310
  * throws the rejection in (a post-resume trap).
@@ -334,8 +345,21 @@ export declare function createLoweredImport(input: {
334
345
  stats: ExecutionStats;
335
346
  /** Executor's suspension mode; decides whether a sync lower may park. */
336
347
  mode: SuspensionMode;
337
- /** Host fn carries the `suspending()` brand (embedder-api.md A1). */
348
+ /** Host fn carries the `suspending()` brand (embedder-api.md suspending mark). */
338
349
  suspendable: boolean;
350
+ /**
351
+ * Host fn carries the `deferCancel()` brand (embedder-api.md cancellation discard): the
352
+ * import must run to completion, so a cancellation is accepted and ignored
353
+ * instead of taking the default discard.
354
+ */
355
+ deferCancel: boolean;
356
+ /**
357
+ * Host fn carries the `abortable()` brand (embedder-api.md abortable()): every call
358
+ * receives a fresh `AbortSignal` appended after the WIT-declared params, and
359
+ * the runtime aborts it when — and only when — the call is discarded by a
360
+ * guest cancellation.
361
+ */
362
+ abortable: boolean;
339
363
  }): CoreFn;
340
364
  /**
341
365
  * The callback-ABI dispatch loop of `canon_lift` (definitions.py lines
@@ -6,7 +6,8 @@ import { type ExecutionStats } from "./boundary.js";
6
6
  /**
7
7
  * Host-provided imports: a nested record keyed by the component's *exact*
8
8
  * import strings. A plan import with a non-empty `path` (an item extracted
9
- * from an imported instance — plan-format.md v0.1 amendment #4) is looked up
9
+ * from an imported instance — `imports[].path`, contracts/plan-format.md
10
+ * schema) is looked up
10
11
  * by walking `imports[name]` then each path segment in order. So an import
11
12
  * of `"ns:pkg/iface"` exposing `f` is supplied as
12
13
  * `{ "ns:pkg/iface": { f: (…) => … } }`.
@@ -65,7 +66,7 @@ export interface InstantiateInput {
65
66
  * returns a Promise (empirical fact (e) — `WebAssembly.promising` always
66
67
  * does), which is an API-shape change. Ignored on an engine without JSPI,
67
68
  * where every blocking site keeps raising the precise `NeedsJspi` it raises
68
- * today (the M3 degradation path).
69
+ * today (the browser-matrix degradation path; see `just browsers`).
69
70
  */
70
71
  jspi?: boolean;
71
72
  /**
@@ -41,7 +41,7 @@ export declare class HostBuffer {
41
41
  advanceBytes(k: number): void;
42
42
  endWindow(): void;
43
43
  }
44
- /** The scoped landing zone handed to a `writeDirect` producer (A21, #128). */
44
+ /** The scoped landing zone handed to a `writeDirect` producer (direct-access byte edge, #128). */
45
45
  export interface DirectDestination {
46
46
  /**
47
47
  * The reader's still-unfilled bytes. Re-derived on every call (a
@@ -56,7 +56,7 @@ export interface DirectDestination {
56
56
  */
57
57
  markWritten(n: number): void;
58
58
  }
59
- /** The scoped view handed to a `readDirect` consumer (A21, #128). */
59
+ /** The scoped view handed to a `readDirect` consumer (direct-access byte edge, #128). */
60
60
  export interface DirectSource {
61
61
  /**
62
62
  * The writer's unread bytes; read-only by contract. Same scoping and
@@ -66,14 +66,14 @@ export interface DirectSource {
66
66
  /** Acknowledge bytes consumed from the view. See `markWritten`. */
67
67
  markRead(n: number): void;
68
68
  }
69
- /** The callback's poll cadence, spelled event-style (A21). */
69
+ /** The callback's poll cadence, spelled event-style. */
70
70
  export type DirectVerdict = "more" | "done";
71
71
  /**
72
72
  * Out-parameter of the low-level direct forms: `true` iff the session ended
73
73
  * because the callback itself returned `"done"`, rather than because the peer
74
74
  * dropped / the operation was cancelled / the peer's instance trapped.
75
75
  *
76
- * The conventions layer needs the distinction for A7 precision — a session
76
+ * The conventions layer needs the distinction for loud component fault precision — a session
77
77
  * the producer already completed keeps its resolution even if the peer then
78
78
  * trapped — and `Promise<number>` is the contract's return shape, so it rides
79
79
  * here rather than in the resolved value.
@@ -110,7 +110,7 @@ export interface HostWritableEnd<T> {
110
110
  writeAll(values: T[]): Promise<number>;
111
111
  /**
112
112
  * Park a **direct session** on this end (`stream<u8>` only — embedder-api
113
- * amendment A21, polyengine#128).
113
+ * §"Streams and futures" ("Direct-access byte edges") (polyengine#128)).
114
114
  *
115
115
  * At every rendezvous with a reader of nonzero capacity, `produce` runs
116
116
  * exactly once, synchronously, inside the rendezvous, with a
@@ -138,9 +138,9 @@ export interface HostWritableEnd<T> {
138
138
  drop(): void;
139
139
  /**
140
140
  * Fire `fn` once the stream becomes dropped — by either end, including
141
- * the A7 teardown walk (immediately, if it already is). The embedder's
141
+ * the loud component fault teardown walk (immediately, if it already is). The embedder's
142
142
  * producer pump uses it to cancel a producer parked on an external
143
- * event (amendment A13's cancellation companion).
143
+ * event (§"Streams and futures"'s cancellation companion).
144
144
  */
145
145
  onDropped(fn: () => void): void;
146
146
  }
@@ -155,7 +155,7 @@ export interface HostReadableEnd<T> {
155
155
  read(max: number): Promise<T[]>;
156
156
  /**
157
157
  * Park a **direct session** on this end (`stream<u8>` only — embedder-api
158
- * amendment A21, polyengine#128). The mirror of
158
+ * §"Streams and futures" ("Direct-access byte edges") (polyengine#128)). The mirror of
159
159
  * `HostWritableEnd.writeDirect`: `consume` receives a `DirectSource` over
160
160
  * the writer's unread bytes (a view of guest memory, or of the offered
161
161
  * host chunk itself) and may take a prefix — a partial take is normal, and
@@ -1,5 +1,5 @@
1
- /** Instantiate-time failure for functionality scheduled after M0. */
1
+ /** Instantiate-time failure for functionality gated on a missing capability. */
2
2
  export declare class UnsupportedFeatureError extends Error {
3
- milestone: "M1" | "M2" | "M2-streams" | "M2-jspi";
4
- constructor(milestone: "M1" | "M2" | "M2-streams" | "M2-jspi", what: string);
3
+ capability: "resources" | "task-core" | "streams" | "jspi";
4
+ constructor(capability: "resources" | "task-core" | "streams" | "jspi", what: string);
5
5
  }
@@ -182,6 +182,6 @@ export interface TrampolineContext {
182
182
  * unsupported kind fails instantiation, not the first call
183
183
  * (plan-format.md "Executor obligations"). Unreferenced trampolines are
184
184
  * never created and therefore never fail (intrinsics.md §B tolerates e.g.
185
- * an unreferenced task-return until M2).
185
+ * an unreferenced task-return until the task core exists).
186
186
  */
187
187
  export declare function createTrampoline(decl: WireTrampoline, ctx: TrampolineContext): CoreFn;
@@ -99,8 +99,8 @@ export declare function createFutureTransfer(ctx: AsyncTransferContext): CoreFn;
99
99
  * rather than removing it, so the source keeps its own.
100
100
  */
101
101
  /**
102
- * Plan v3 (contracts/plan-format.md v3 amendment 2): `instanceOf` resolves
103
- * through the plan's `errorContextTables` section — the
102
+ * `instanceOf` resolves through the plan's `errorContextTables` section
103
+ * (contracts/plan-format.md schema) — the
104
104
  * `TypeComponentLocalErrorContextTableIndex` space these arguments actually
105
105
  * live in. It replaced a resource-table lookup, which shared neither the
106
106
  * index space nor (in a multi-instance composition) the answer.