@polyengine/runtime 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.
Files changed (81) 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 +132 -37
  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 +123 -161
  28. package/esm/exec/executor.js +37 -25
  29. package/esm/exec/host_streams.js +31 -31
  30. package/esm/intrinsics/async_builtins.js +15 -7
  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 +5 -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 +55 -44
  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/plan/format.d.ts +11 -10
  75. package/types/plan/loader.d.ts +2 -2
  76. package/types/shim/translator.d.ts +2 -2
  77. package/types/task/mod.d.ts +26 -97
  78. package/types/task/scheduler.d.ts +81 -62
  79. package/types/task/streams.d.ts +23 -38
  80. package/types/task/subtask.d.ts +2 -2
  81. package/types/task/waitable.d.ts +0 -1
@@ -8,7 +8,8 @@ export type SuspensionMode = "plain" | "jspi";
8
8
  * `requested` is the embedder's opt-in. We additionally require the engine to
9
9
  * actually implement JSPI: on an engine without it every blocking site falls
10
10
  * back to the precise `NeedsJspi` it raised before this module existed, which
11
- * is the M3 browser-matrix degradation path (docs/milestones.md M3).
11
+ * is the browser-matrix degradation path for engines without JSPI (see
12
+ * `just browsers`).
12
13
  */
13
14
  export declare function chooseMode(requested: boolean | undefined, needed?: boolean): SuspensionMode;
14
15
  /**
@@ -140,8 +141,8 @@ export declare class SuspensionPoint<T = unknown> implements SchedulableThread {
140
141
  * transition this point takes — produce-success, produce-throw, or
141
142
  * `abandon` (issue #102). It is the seam a blocking built-in uses to
142
143
  * discharge state it owns for the duration of the park (the FACT
143
- * start-calls' borrow-lender scopes, contracts/intrinsics.md v0.2
144
- * amendment 2) without having to trust that `produce` runs.
144
+ * start-calls' borrow-lender scopes, contracts/intrinsics.md §A's
145
+ * trap-unwind/lender-release obligation) without having to trust that `produce` runs.
145
146
  *
146
147
  * INVARIANTS this hook must respect, so bridge.ts's own contracts are
147
148
  * not disturbed:
@@ -180,8 +181,8 @@ export declare class SuspensionPoint<T = unknown> implements SchedulableThread {
180
181
  * transition this point takes — produce-success, produce-throw, or
181
182
  * `abandon` (issue #102). It is the seam a blocking built-in uses to
182
183
  * discharge state it owns for the duration of the park (the FACT
183
- * start-calls' borrow-lender scopes, contracts/intrinsics.md v0.2
184
- * amendment 2) without having to trust that `produce` runs.
184
+ * start-calls' borrow-lender scopes, contracts/intrinsics.md §A's
185
+ * trap-unwind/lender-release obligation) without having to trust that `produce` runs.
185
186
  *
186
187
  * INVARIANTS this hook must respect, so bridge.ts's own contracts are
187
188
  * not disturbed:
@@ -53,10 +53,9 @@ export interface WirePlan {
53
53
  * `ResourceIndex = importedResources.length + DefinedResourceIndex`
54
54
  * (wasmtime `Component::resource_index`).
55
55
  *
56
- * plan-format.md v0.1 amendment #2 documents this as a *gap*; the field is
57
- * a **v0.2 proposal** emitted by the shim. Optional here so plans produced
58
- * by a v0.1 shim still load — absent is read as "no imported resources",
59
- * which is exactly what v0.1 asserted.
56
+ * The `importedResources` field (contracts/plan-format.md schema); optional
57
+ * here so plans produced by an older shim still load absent is read as
58
+ * "no imported resources".
60
59
  */
61
60
  importedResources?: WireImportedResource[];
62
61
  imports: WireImport[];
@@ -157,9 +156,9 @@ export interface WireExportItem {
157
156
  }
158
157
  /**
159
158
  * Trampoline declarations, tag-for-tag with the wasmtime `Trampoline` enum.
160
- * Only the M0-relevant variants are given precise field types; the rest are
161
- * matched by `kind` and rejected at instantiate time with milestone-aware
162
- * errors (contracts/intrinsics.md §B).
159
+ * Only the variants the executor implements are given precise field types;
160
+ * the rest are matched by `kind` and rejected at instantiate time with
161
+ * capability-aware errors (contracts/intrinsics.md §B).
163
162
  * @internal
164
163
  */
165
164
  export type WireTrampoline = {
@@ -190,8 +189,9 @@ export type WireTrampoline = {
190
189
  */
191
190
  results: number;
192
191
  /**
193
- * `results` interned into `plan.types` as a tuple type (plan v3,
194
- * contracts/plan-format.md v3 amendment 3). `null` is accepted on the
192
+ * `results` interned into `plan.types` as a tuple type (the task-return
193
+ * trampoline's raw `results` key + interned `resultType`;
194
+ * contracts/plan-format.md schema). `null` is accepted on the
195
195
  * wire for a task with no declared result type; the current producer
196
196
  * never emits it (wasmtime's `TaskReturn.results` is not an `Option` —
197
197
  * a no-result task carries the empty tuple).
@@ -343,7 +343,8 @@ export type WireExport = {
343
343
  }
344
344
  /**
345
345
  * An exported embedded core module; `module` indexes the static module
346
- * space (`modules`). plan-format.md v4 amendment 2.
346
+ * space (`modules`); the `module` export kind (contracts/plan-format.md
347
+ * schema notes).
347
348
  */
348
349
  | {
349
350
  kind: "module";
@@ -35,7 +35,7 @@ export declare class TranslateError extends Error {
35
35
  * the *resource*-table mapping before, a different space) — and
36
36
  * `task-return`'s `resultType` / raw `results` split, which lets a FACT
37
37
  * callee task carry its declared result type.
38
- * v2 (M2 phase 2c): `streamTables` / `futureTables` — the element types the
38
+ * v2: `streamTables` / `futureTables` — the element types the
39
39
  * stream and future built-ins need to size their copy buffers.
40
40
  * v1 (contracts/plan-format.md v0.3): `CoreDef` gained `"unsafe-intrinsic"`.
41
41
  * The change is purely additive, but the contract's compat rule is a strict
@@ -72,7 +72,7 @@ export interface LoadedPlan {
72
72
  /**
73
73
  * Number of imported resource types. `ResourceIndex =
74
74
  * numImportedResources + DefinedResourceIndex`
75
- * (plan-format.md v0.1 amendment #2 / v0.2 `importedResources`).
75
+ * (the `importedResources` field; contracts/plan-format.md schema).
76
76
  */
77
77
  numImportedResources: number;
78
78
  /** Element type per stream table (plan v2); `null` = zero-width payload. */
@@ -18,8 +18,8 @@ export declare class Translator {
18
18
  * (docs/architecture.md §10): the wire envelope's `producer` block records
19
19
  * `{shimVersion, wasmtimeEnviron, features}`, which does NOT change when
20
20
  * the shim wasm is rebuilt from the same source versions (e.g. a local
21
- * patch or a different toolchain producing different codegen) — see the
22
- * M3-B dispatch. Digesting the actual bytes is the only sound cache key
21
+ * patch or a different toolchain producing different codegen). Digesting
22
+ * the actual bytes is the only sound cache key
23
23
  * component for translator identity.
24
24
  */
25
25
  readonly buildHash: string | null;
@@ -1,4 +1,5 @@
1
1
  import { Table } from "../cabi/handles.js";
2
+ import { COMPONENT_INSTANCE } from "../cabi/context.js";
2
3
  import type { ComponentInstanceLike } from "../cabi/context.js";
3
4
  import type { ComponentValue, FuncType } from "../cabi/types.js";
4
5
  import { type Cancelled, Store } from "./scheduler.js";
@@ -10,8 +11,6 @@ export * from "./thread.js";
10
11
  export * from "./waitable.js";
11
12
  export * from "./subtask.js";
12
13
  export * from "./streams.js";
13
- /** Constructor marker: "this one IS the root, do not give it a parent". */
14
- declare const ROOT_TOKEN: unique symbol;
15
14
  /** Anything a component instance's handle table can hold. */
16
15
  export type HandleTableEntry = unknown;
17
16
  /**
@@ -21,8 +20,19 @@ export type HandleTableEntry = unknown;
21
20
  * `mayLeave` is backed by a real `WebAssembly.Global(i32, mutable)` because
22
21
  * FACT adapters import that global (`flags` namespace) and read/write it as
23
22
  * the may_leave boolean (wasmtime 47 FACT treats the whole flags global as
24
- * may_leave; there is no bitmask). Initial value 1 (true). `mayEnter` is
25
- * host-side state: nothing wasm-visible reads it.
23
+ * may_leave; there is no bitmask). Initial value 1 (true).
24
+ *
25
+ * There is no `may_enter` counterpart and no instance tree: at the pinned
26
+ * reference (definitions.py @ 2f13265, CM#705) there is no `may_enter`,
27
+ * `parent`, `entering_set`, `enter_from` or `leave_to`, so nothing gates
28
+ * entry into a live instance. What polyengine adds beyond the reference is
29
+ * per-instance POISONING — a named divergence
30
+ * living entirely in ./scheduler.ts (`isInstancePoisoned`, `entryRefusal`),
31
+ * not in any state on this class.
32
+ *
33
+ * `COMPONENT_INSTANCE` brands this class as a real component instance for
34
+ * the layers that only see the structural `ComponentInstanceLike`
35
+ * (cabi/handles.ts `isComponentInstance`; cabi must not import task/).
26
36
  */
27
37
  export declare class ComponentInstanceState implements ComponentInstanceLike {
28
38
  readonly index: number;
@@ -30,103 +40,18 @@ export declare class ComponentInstanceState implements ComponentInstanceLike {
30
40
  handles: Table<HandleTableEntry>;
31
41
  /** definitions.py `ComponentInstance.threads` — a Table, so `thread.index`. */
32
42
  readonly threads: Table<Thread>;
33
- mayEnter: boolean;
43
+ /** cabi's real-instance discriminator; see the class doc. */
44
+ readonly [COMPONENT_INSTANCE] = true;
34
45
  /** definitions.py `backpressure: int` — a *counter* (backpressure.{inc,dec}). */
35
46
  backpressure: number;
36
47
  /** definitions.py `num_waiting_to_enter`. */
37
48
  numWaitingToEnter: number;
38
49
  /** definitions.py `exclusive_thread`. */
39
50
  exclusiveThread: Thread | null;
40
- /**
41
- * definitions.py `ComponentInstance.parent`.
42
- *
43
- * The plan still gives us a flat instance space, but the tree is no longer
44
- * needed: every instance of one instantiation gets the same **synthetic
45
- * root** as its parent (contracts/plan-format.md v3 amendment 4 /
46
- * polyengine#101). See `enteringSet` for why that is observably equivalent to
47
- * the real chain. The root itself has no parent.
48
- */
49
- parent: ComponentInstanceState | null;
50
51
  readonly store: Store;
51
- /**
52
- * The synthetic per-instantiation root (v3 amendment 4). One per `Store`:
53
- * a `Store` is exactly one component instantiation's scheduling scope, so
54
- * "all `ComponentInstanceState`s sharing a `Store`" is the set that shares
55
- * a top-level component — which is the granularity wasmtime's own
56
- * top-level-instance-id comparison uses (concurrent.rs:1876-1886).
57
- *
58
- * It is a real `ComponentInstanceState` (index -1) rather than a bare flag
59
- * so it flows through `selfAndAncestors`/`enteringSet` unchanged; its
60
- * handle table stays empty and no task ever runs on it.
61
- */
62
- static rootOf(store: Store): ComponentInstanceState;
63
- /** Is this the synthetic root (never a real component instance)? */
64
- get isSyntheticRoot(): boolean;
65
- constructor(index: number, store?: Store, root?: typeof ROOT_TOKEN);
66
- /**
67
- * Release the synthetic root after a trap broke the enter/leave bracket
68
- * (v3 amendment 4, and a **named divergence** from the reference).
69
- *
70
- * definitions.py poisons the whole entering set: `Store.lift` never reaches
71
- * `leave_to`, so the root — which is in every host entry's entering set —
72
- * stays `may_enter == False` forever and NO instance of the component can
73
- * be entered again. wasmtime is the same by other means (it poisons the
74
- * store). polyengine deliberately supports post-trap re-entry of instances the
75
- * trap did not touch (exec/boundary.ts `poison`: "sibling instances stay
76
- * usable, which is why the lock is released per-instance rather than by
77
- * poisoning a whole store the way wasmtime does"), and the synthetic root
78
- * must not silently convert that documented divergence into store-wide
79
- * poisoning. So a trap poisons the LEAF set only, and the root is released
80
- * here — the reentrance gate the root exists for (a *second, concurrent*
81
- * host entry) is about a live entry, and after a trap unwinds to the host
82
- * there is none.
83
- */
84
- releaseSyntheticRootOnPoison(): void;
52
+ constructor(index: number, store?: Store);
85
53
  get mayLeave(): boolean;
86
54
  set mayLeave(v: boolean);
87
- /** definitions.py `ComponentInstance.self_and_ancestors` (line 236). */
88
- selfAndAncestors(): Set<ComponentInstanceState>;
89
- /**
90
- * definitions.py `ComponentInstance.entering_set` (line 230):
91
- * `self_and_ancestors() - caller.self_and_ancestors()`.
92
- *
93
- * CONTRACT (contracts/plan-format.md v3 amendment 4, polyengine#101): the plan
94
- * still carries no wire form for the component-instance tree, and it no
95
- * longer needs one. Every instance's parent is the synthetic
96
- * per-instantiation root, so:
97
- *
98
- * * host entry (`caller === null`): `{this, root}` — the reference's
99
- * entering set for a host entry is `self_and_ancestors()`, which always
100
- * contains the top-level root, so a second host entry anywhere in the
101
- * tree trips on the root either way. This is the divergence #101
102
- * reported (host -> A.f -> host import -> host enters a *different*
103
- * instance): now caught.
104
- * * guest-to-guest (`caller !== null`): `{this}` — the root is in the
105
- * caller's ancestor set and cancels out. Intermediate ancestors would
106
- * be the only difference from the real chain, and they are never
107
- * reachably consulted: FACT compiles same-instance and ancestor calls
108
- * to unconditional compile-time traps, and sibling cycles are
109
- * unreachable because instance imports form a DAG (polyengine#99
110
- * adjudication).
111
- *
112
- * So the synthetic root is observably equivalent to the full chain, and it
113
- * matches wasmtime's own shortcut — a top-level instance-id comparison
114
- * (concurrent.rs:1876-1886) — by construction. This reopens only if some
115
- * future upstream shape makes nesting depth observable.
116
- *
117
- * One deliberate departure remains, at the trap path rather than here: see
118
- * `releaseSyntheticRootOnPoison`.
119
- */
120
- enteringSet(caller: ComponentInstanceState | null): Set<ComponentInstanceState>;
121
- /** definitions.py `ComponentInstance.may_enter_from` (line 214). */
122
- mayEnterFrom(caller: ComponentInstanceState | null): boolean;
123
- /** definitions.py `ComponentInstance.enter_from` (line 220). */
124
- enterFrom(caller: ComponentInstanceState | null): void;
125
- /** definitions.py `ComponentInstance.leave_to` (line 225). */
126
- leaveTo(caller: ComponentInstanceState | null): void;
127
- /** Backwards-compatible host-entry helpers (the M0 spelling). */
128
- enter(): void;
129
- leave(): void;
130
55
  }
131
56
  /** definitions.py `Task.State` (line 445). */
132
57
  export type TaskState = "initial" | "started" | "pending-cancel" | "cancel-delivered" | "resolved";
@@ -190,7 +115,8 @@ export declare class Task {
190
115
  * A FACT callee task's result type arrives as the raw wasmtime
191
116
  * `TypeTupleIndex` `prepare-call` passes as `task_return_type`; v3's
192
117
  * `task-return.results` / `resultType` pair is the dictionary for it
193
- * (contracts/plan-format.md v3 amendment 3). It resolves for every callee
118
+ * (the task-return trampoline's raw `results` key + interned `resultType`;
119
+ * contracts/plan-format.md schema). It resolves for every callee
194
120
  * that has a `task.return` trampoline of its own — which is every callee
195
121
  * that can call `task.return` — but a callee with none (sync-lifted,
196
122
  * reached through an async-to-sync adapter) contributes no entry, and then
@@ -238,10 +164,13 @@ export declare class Task {
238
164
  /** definitions.py `Task.unregister_thread` (line 510). */
239
165
  unregisterThread(thread: Thread): void;
240
166
  /**
241
- * definitions.py `Task.request_cancellation` (line 519). Delivered to a
242
- * cancellable thread if one exists and the instance is enterable; otherwise
243
- * recorded as pending, to be picked up at the next cancellable block point
244
- * (`deliverPendingCancel`).
167
+ * definitions.py `Task.request_cancellation` (@ 2f13265). Delivered to a
168
+ * cancellable thread if one exists; otherwise recorded as pending, to be
169
+ * picked up at the next cancellable block point (`deliverPendingCancel`).
170
+ *
171
+ * `caller` is retained for the call-site shape (fact_calls.ts's
172
+ * `subtask.onCancel`) and for diagnostics; no condition here consults it
173
+ * (CM#705: entry into a live instance is ungated).
245
174
  */
246
175
  requestCancellation(caller: ComponentInstanceState | null): void;
247
176
  /** definitions.py `Task.deliver_pending_cancel` (line 536). */
@@ -46,8 +46,28 @@ export declare class NeedsJspi extends Error {
46
46
  }
47
47
  export declare function needsJspi(what: string): never;
48
48
  /**
49
- * Failure raised where a capability scheduled for a later M2 phase is
50
- * required. Same rationale as `NeedsJspi`: never a `Trap`.
49
+ * Failure raised when a synchronous entry into an instance would race a
50
+ * pending lift (contracts/embedder-api.md §"Functions and async",
51
+ * failure-ladder arm 2).
52
+ *
53
+ * In jspi mode a promising-wrapped entry settles through a microtask hop even
54
+ * when nothing suspended, and the hop-quiescence gate (exec/boundary.ts)
55
+ * defers Promise-surface calls that would enter during that window. A
56
+ * synchronous caller — a resource constructor, or the embedder's `sync()`
57
+ * adapter — cannot be deferred, so it refuses instead.
58
+ *
59
+ * Deliberately *not* a `Trap`, and deliberately raised BEFORE the instance is
60
+ * entered: nothing was entered, so there is nothing to poison. The refusal is
61
+ * transient — the instance stays enterable, and the call succeeds on retry
62
+ * once the in-flight activity settles, or immediately through the
63
+ * Promise-shaped surface, which defers rather than refusing.
64
+ */
65
+ export declare class SyncEntryBusy extends Error {
66
+ constructor(what: string);
67
+ }
68
+ /**
69
+ * Failure raised where a not-yet-implemented capability is required. Same
70
+ * rationale as `NeedsJspi`: never a `Trap`.
51
71
  */
52
72
  export declare class PendingCapability extends Error {
53
73
  constructor(what: string);
@@ -65,17 +85,6 @@ export declare function notifyInstancePoisoned(inst: {
65
85
  handles: Iterable<unknown>;
66
86
  }, cause: unknown): void;
67
87
  export declare function isInstancePoisoned(inst: object): boolean;
68
- /**
69
- * May a settled activation tail for parked thread `t` be DISPATCHED now
70
- * (issue #156)? True iff its instance is host-enterable — `Thread.resumeWith`
71
- * brackets the resumption with `enterFrom(null)` — or POISONED, in which case
72
- * `resumeWith`'s early return retires it and deferring would leak forever.
73
- *
74
- * CONTRACT: a parked entry without a reachable `task.inst` (the partial
75
- * thread doubles the host-pump tests park in `Store.awaiting`) holds no
76
- * reentrance state, so there is nothing to defer on: dispatchable.
77
- */
78
- export declare function dispatchableTail(t: any): boolean;
79
88
  /**
80
89
  * The recorded cause of an instance's poisoning: the original trap that
81
90
  * broke the enter/leave bracket (polyengine#145). `undefined` when the instance
@@ -85,16 +94,39 @@ export declare function dispatchableTail(t: any): boolean;
85
94
  export declare function instancePoisonCause(inst: object): unknown;
86
95
  /**
87
96
  * Append the recorded poison cause to an entry-refusal trap message
88
- * (polyengine#145 ask 1). "cannot enter component instance" covers two states
89
- * that send an embedder down entirely different debugging paths a
90
- * transient reentrance overlap (retry later, look for caller-side call
91
- * overlap) and a permanently poisoned instance (the corpse of an earlier
92
- * trap, which this suffix names). Only the poisoned case gets the suffix:
93
- * the transient message stays byte-identical, and the suffix is
97
+ * (polyengine#145 ask 1). "cannot enter component instance" has exactly one
98
+ * cause a permanently poisoned instance, the corpse of an earlier trap
99
+ * and this
100
+ * suffix names the trap that made it one. The call is kept unconditional at
101
+ * the refusal sites (returning `base` unchanged for an unmarked instance) so
102
+ * the message construction stays in one place; the suffix is
94
103
  * conformance-safe because the official suite matches trap messages by
95
104
  * substring (harness/src/runner.ts).
96
105
  */
97
106
  export declare function withPoisonCause(inst: object, base: string): string;
107
+ /**
108
+ * The entry-refusal decision, in one place: may `caller` enter `callee` right
109
+ * now, and if not, what does the refusal trap say? Returns `null` when entry
110
+ * is allowed, otherwise the exact trap message for `base`.
111
+ *
112
+ * POISONING IS THE WHOLE MECHANISM (CM#705). There is no transient
113
+ * reentrance gate: at the pinned reference (definitions.py @ 2f13265)
114
+ * `may_enter`, `entering_set`, `enter_from`, `leave_to` and
115
+ * `ComponentInstance.parent` do not exist — `Store.lift` runs `canon_lift`
116
+ * with no gate at all, so host-mediated reentrance into a live instance is
117
+ * simply VALID.
118
+ *
119
+ * Against that, per-instance poisoning is polyengine's NAMED DIVERGENCE. A
120
+ * trapped instance is a corpse — entry is refused permanently, with the
121
+ * recorded cause appended (polyengine#145 ask 1) — where wasmtime instead
122
+ * kills the whole store. The reference never faces the question because a
123
+ * trap there is the end of the world.
124
+ *
125
+ * The `caller !== callee` guard keeps a self-call out of the refusal: a dtor
126
+ * invoked from inside its own instance (cabi/handles.ts) is the live case —
127
+ * it must not be refused by its own instance's marker.
128
+ */
129
+ export declare function entryRefusal(callee: object, caller: unknown, base: string): string | null;
98
130
  /** Test hook: switch policy at runtime. `null` restores FIFO. */
99
131
  export declare function schedulerSeedForTesting(value: number | null): void;
100
132
  /**
@@ -277,22 +309,18 @@ export declare class Store {
277
309
  * wedges the loops, because an activation that merely hopped legitimately
278
310
  * holds an ambient while the scheduler is free to proceed.
279
311
  *
280
- * PER-STORE and MULTI-ENTRY since 2026-08-22 (issues #158 mechanism B,
281
- * #210). It was one module-global slot with a one-claimant assert, which
282
- * (a) could not represent two legitimately-pending engine resumptions a
283
- * running activation X delivering a resume to Z while Y's resumption was
284
- * still pending crashed on the assert and (b) made every driver on every
285
- * store yield while ANY store held a claim, so an idle store's
286
- * `driveStoreAsync` died at the 10,000-hop assert (~311ms) while another
287
- * store merely dwelt on a slow host import. The assert's invariant was
288
- * tier-3 attribution unambiguity, which no longer exists (see
289
- * `resolveAmbient`), so it is gone with the slot; the entries and their
290
- * release edges are otherwise unchanged, per entry.
312
+ * PER-STORE and MULTI-ENTRY (issues #158 mechanism B, #210), both load
313
+ * bearing. MULTI-ENTRY because two engine resumptions can legitimately be
314
+ * pending at once: a running activation X may deliver a resume to Z while
315
+ * Y's resumption is still outstanding, and a one-claimant gate cannot
316
+ * represent that. PER-STORE because a claim held store-wide makes every
317
+ * driver on EVERY store yield: an idle store's `driveStoreAsync` dies at
318
+ * the 10,000-hop assert (~311ms) while another store merely dwells on a
319
+ * slow host import.
291
320
  *
292
321
  * Cross-store de-serialization is safe by disjointness: an activation
293
- * belongs to exactly one store. Same-store it is strictly more conservative
294
- * than the old slot — the gate keeps refusing until EVERY pending entry has
295
- * died, rather than crashing on the second.
322
+ * belongs to exactly one store. Same-store the set is conservative the
323
+ * gate keeps refusing until EVERY pending entry has died.
296
324
  *
297
325
  * Release edges, per entry: the activation PARKS again
298
326
  * (`blockCurrentActivation` -> `consumePendingIfRunning`), it FINISHES (its
@@ -395,37 +423,28 @@ export declare class Store {
395
423
  * throw (trap unwinding); callers propagate or park it exactly as they do
396
424
  * for `tick`.
397
425
  *
398
- * A tail whose instance is NOT host-enterable is DEFERRED IN PLACE — left
399
- * in the queue, skipped here until the lock releases (issue #156).
400
- * `resumeWith` brackets the resumption with `enterFrom(null)`, and under
401
- * the shared synthetic per-instantiation root a host entry into ANY
402
- * instance of the graph locks the root, so while one instance is entered a
403
- * sibling's tail cannot be dispatched: dispatching it tripped
404
- * `resumeWith`'s enterability assert (which, mutating before asserting,
405
- * also stranded the thread and lost the settle).
426
+ * Every non-stale tail is dispatched immediately, in queue order: there is
427
+ * no enterability condition to defer on (CM#705).
406
428
  *
407
- * Deferral is safe because `!inst.mayEnterFrom(null)` is EXACTLY `tick`'s
408
- * candidate-filter predicate on the same instance: while a tail of `inst`
409
- * is deferred, `tick` cannot resume any thread of `inst` either, so the
410
- * phantom-state gate the queue exists to enforce is preserved per-instance
411
- * by construction.
429
+ * The ordering discipline is therefore settle order, full stop — and it is
430
+ * the reason this queue exists rather than a direct resumption from the
431
+ * settle continuation: in definitions.py the tail runs atomically inside
432
+ * the entered bracket, so the phantom-state gate (`tick` refuses while an
433
+ * unserviced tail is queued, see `hasServiceableSettled`) is what keeps a
434
+ * parked activation's tail from being observed out of order.
412
435
  *
413
- * The ordering discipline is therefore per-instance settle order. Cross-
414
- * instance order relaxes only when enterability defers a tail, which is
415
- * conforming schedule nondeterminism: in definitions.py the tail runs
416
- * atomically inside the entered bracket, so a host entry admitted during a
417
- * park necessarily orders before the parked activation's tail there.
418
- *
419
- * A POISONED instance's tail is still dispatched: `resumeWith`'s poison
420
- * early-return retires it, and deferring it would leak forever — a
421
- * poisoned leaf keeps its lock permanently.
436
+ * A POISONED instance's tail is dispatched like any other: `resumeWith`'s
437
+ * poison early-return retires it, so it drains rather than leaking.
422
438
  */
423
439
  serviceSettled(): boolean;
424
440
  /**
425
- * "Would a `serviceSettled` call make progress right now?" — i.e. some
426
- * entry is stale (would be removed) or serviceable (would be dispatched).
427
- * A queue holding ONLY deferred tails (issue #156) answers false: `tick`
428
- * must not be gated by them, and the driving loops must not spin on them.
441
+ * "Would a `serviceSettled` call make progress right now?" — i.e. is any
442
+ * entry queued at all. Every entry either dispatches or is dropped as
443
+ * stale, so a non-empty queue always makes progress.
444
+ *
445
+ * It exists to gate `tick` (and to keep the driving loops from parking)
446
+ * behind unserviced tails: resuming some other thread while a settled tail
447
+ * waits would expose the out-of-order state the queue is there to prevent.
429
448
  */
430
449
  hasServiceableSettled(): boolean;
431
450
  /**
@@ -473,9 +492,9 @@ export declare class Store {
473
492
  */
474
493
  hasRunnableWork(inst: unknown, excludeTask: unknown): boolean;
475
494
  /**
476
- * definitions.py `Store.tick` (line 597): resume one ready thread, bracketed
477
- * by the reentrance gate for a host-initiated entry (`enter_from(None)` /
478
- * `leave_to(None)`).
495
+ * definitions.py `Store.tick` (@ 2f13265): resume one ready thread. There
496
+ * is no bracket and no gate the reference body is exactly "pick a ready
497
+ * thread, resume it" (CM#705).
479
498
  *
480
499
  * Returns false when no thread was ready, so callers can distinguish
481
500
  * "made progress" from "stuck" without inspecting the queue themselves.
@@ -77,7 +77,7 @@ export interface RendezvousBuffer {
77
77
  write(vs: PayloadChunk): void;
78
78
  }
79
79
  /**
80
- * A21: the peer half of a direct rendezvous — a buffer that can expose its
80
+ * direct-access byte edge: the peer half of a direct rendezvous — a buffer that can expose its
81
81
  * remaining range as bytes and be advanced without a copy.
82
82
  *
83
83
  * Implemented by `GuestBuffer` (a view into guest linear memory: the
@@ -99,7 +99,7 @@ export interface ByteWindow {
99
99
  endWindow?(): void;
100
100
  }
101
101
  /**
102
- * A21: the parked direct session, as the rendezvous sees it. It presents the
102
+ * direct-access byte edge: the parked direct session, as the rendezvous sees it. It presents the
103
103
  * ordinary buffer surface (so `remain()`/`isZeroLength()` keep the reference
104
104
  * control flow working) but its `read`/`write` are never called — the seam
105
105
  * routes it through `runDirect` instead.
@@ -159,7 +159,7 @@ export declare class SharedStreamImpl implements SharedBase {
159
159
  * OUT of a guest table. The receiver — the host, or the destination of a
160
160
  * guest-to-guest hop, in which case the immediately following lower fires
161
161
  * `onLowered` — may now act on the transferred end. Host wrappers use it to
162
- * re-arm their activity (#162, embedder-api amendment A15). Guest-owned
162
+ * re-arm their activity (#162, contracts/embedder-api.md §"Streams and futures"). Guest-owned
163
163
  * objects leave it unset.
164
164
  */
165
165
  onLifted: ((inst: {
@@ -210,7 +210,7 @@ export declare class SharedFutureImpl implements SharedBase {
210
210
  * OUT of a guest table. The receiver — the host, or the destination of a
211
211
  * guest-to-guest hop, in which case the immediately following lower fires
212
212
  * `onLowered` — may now act on the transferred end. Host wrappers use it to
213
- * re-arm their activity (#162, embedder-api amendment A15). Guest-owned
213
+ * re-arm their activity (#162, contracts/embedder-api.md §"Streams and futures"). Guest-owned
214
214
  * objects leave it unset.
215
215
  */
216
216
  onLifted: ((inst: {
@@ -351,51 +351,35 @@ interface PoisonedInstanceLike {
351
351
  * **poisoned** guest instance is retired silently via `resetPending`.
352
352
  * Notifying it would queue a phantom event into the corpse's waitables, and
353
353
  * a later driving loop servicing it would resume machinery whose instance
354
- * can no longer be entered (`tick` asserts enterability). Host sentinels are
354
+ * can no longer be entered (`tick` excludes poisoned instances). Host sentinels are
355
355
  * not instances at all, so they are always notified.
356
356
  *
357
- * #100: THE HEALTH TEST IS "POISONED", NOT "`mayEnter === false`". The
358
- * original test used non-enterability as a proxy for deadness. The proxy is
359
- * unsound in one direction, and the unsoundness stranded healthy tasks:
360
- *
361
- * * (sound half, #84 audit) a healthy guest peer always parks with
362
- * `mayEnter === true`. Every park the callback ABI's waitable-set wait,
363
- * and equally a sync-lowered/JSPI peer blocked inside `finishCopy`'s
364
- * SITE 4 via `blockCurrentActivation` — yields the thread out of the
365
- * scheduler's enter/leave bracket, and the bracket's `leaveTo` runs on the
366
- * way out (task/scheduler.ts `Store.tick` :905-917, task/thread.ts
367
- * `Thread.resumeWith` :157-179, whose resume-side
368
- * `assert_(mayEnterFrom(null))` would fire otherwise). Blocking inside a
369
- * wasm frame does NOT hold the enter bracket.
370
- * * (unsound converse) `mayEnter === false` does not imply "poisoned". An
371
- * instance that is merely mid-call is also non-enterable, and a CALLER
372
- * instance stays non-enterable for the whole duration of a
373
- * cross-component (FACT) call into an instance that traps
374
- * (`ComponentInstanceState.enterFrom` clears `mayEnter` on the callee's
375
- * entering set only, task/mod.ts). A *different*, healthy task of that
376
- * caller, parked on an end of a stream/future the trapping callee also
377
- * held, was classified dead here and retired silently — stranded, the
378
- * exact outcome #66 exists to prevent.
357
+ * #100: THE HEALTH TEST IS "POISONED", NOT "BUSY". Deadness must be judged
358
+ * by the poison marker and nothing weaker: any liveness proxy that also
359
+ * covers a merely mid-call instance is unsound in one direction and strands
360
+ * healthy tasks. Under such a proxy a caller mid cross-component (FACT) call
361
+ * into an instance that trapped would drag its *other*, healthy tasks down
362
+ * with it one parked on an end of a stream/future the trapping callee also
363
+ * held would be classified dead here and retired silently, the exact outcome
364
+ * #66 exists to prevent.
379
365
  *
380
366
  * So the test consults the poison marker itself. It is per-instance and
381
- * recorded at the single seam every bracket-break site routes through
367
+ * recorded at the single seam every poisoning site routes through
382
368
  * (`notifyInstancePoisoned`, task/scheduler.ts: exec/boundary.ts `poison`,
383
369
  * `Store.tick`, `Thread.resumeWith`, the FACT cross-component catches in
384
370
  * intrinsics/fact_calls.ts, and cabi/handles.ts's gated destructor call),
385
371
  * and it is recorded *before* the retirement walk runs, so an instance's own
386
372
  * parked ends still see it during its own walk. `retiredInstances` is
387
373
  * consulted alongside it because the walk is also reachable directly (it is
388
- * set at walk entry, so the two agree); neither ever contains the synthetic
389
- * per-instantiation root, which every poison site skips or releases (plan v3
390
- * amendment 4, `releaseSyntheticRootOnPoison`).
374
+ * set at walk entry, so the two agree).
391
375
  *
392
376
  * Why this does not re-open review B2 (phantom events into a corpse): the
393
377
  * concern is that a DROPPED event queued onto a waitable of an instance that
394
378
  * can never be entered again would be serviced by a later driving loop and
395
- * resume machinery whose `tick` asserts enterability. "Can never be entered
396
- * again" is precisely poisoning — a mid-call instance's `mayEnter` is
397
- * restored by its own `leaveTo` when the call returns, and its parked task
398
- * then resumes normally and consumes the event. The narrowed predicate
379
+ * resume machinery `tick` deliberately excludes. "Can never be entered
380
+ * again" is precisely poisoning — a merely mid-call instance is entirely
381
+ * ordinary, and its parked task resumes normally and consumes the event.
382
+ * The narrowed predicate
399
383
  * therefore excludes exactly the population B2 is about, and admits only
400
384
  * peers that will run again.
401
385
  *
@@ -407,8 +391,9 @@ export declare function dropSharedForTeardown(shared: SharedStreamImpl | SharedF
407
391
  * Retire every live stream/future end in a trap-poisoned instance's handle
408
392
  * table (#66).
409
393
  *
410
- * Rationale: after a trap breaks the enter/leave bracket, `mayEnter` stays
411
- * false forever, so no task of this instance can ever rendezvous again. Its
394
+ * Rationale: a trapped instance is a corpse (polyengine's per-instance
395
+ * poisoning divergence), so no task of this instance can ever rendezvous
396
+ * again. Its
412
397
  * table's `CopyEnd`s are therefore unreachable-forever — leaving their shared
413
398
  * objects live strands the peers: a parked HOST operation never settles (its
414
399
  * promise hangs), and a LATER host operation would "succeed" against the
@@ -416,7 +401,7 @@ export declare function dropSharedForTeardown(shared: SharedStreamImpl | SharedF
416
401
  * Dropping the shared object now converts both into the spec-shaped DROPPED
417
402
  * outcome, and the recorded failure lets the embedder layer brand it.
418
403
  *
419
- * Called from every bracket-break site — exec/boundary.ts `poison()` (the
404
+ * Called from every poisoning site — exec/boundary.ts `poison()` (the
420
405
  * sync-lift path), scheduler.ts `Store.tick` and thread.ts
421
406
  * `Thread.resumeWith` (traps during a resumed thread), and the FACT
422
407
  * cross-component catches (intrinsics/fact_calls.ts, callee side) — with the
@@ -54,8 +54,8 @@ export declare class Subtask extends Waitable {
54
54
  /**
55
55
  * Release a never-delivered subtask's lenders after its call broke off a
56
56
  * non-poisoning exit — trap-rethrow past the CALLEE, capability bail, or
57
- * an abandoned park (contracts/intrinsics.md v0.2 amendment 2, #91 scope
58
- * clarification; the park legs are #102/#106).
57
+ * an abandoned park (contracts/intrinsics.md §A's trap-unwind/lender-release
58
+ * obligation; the park legs are #102/#106).
59
59
  *
60
60
  * The reference has no analogue because it never resumes after a trap:
61
61
  * the store dies with the lent handles inside it. The resolution state
@@ -12,7 +12,6 @@ export declare enum EventCode {
12
12
  }
13
13
  /** definitions.py `EventTuple` = `(EventCode, int, int)`. */
14
14
  export type EventTuple = [code: EventCode, p1: number, p2: number];
15
- export declare const NO_EVENT: EventTuple;
16
15
  /**
17
16
  * definitions.py `class Waitable` (line 767).
18
17
  *