@polyengine/runtime 0.1.0-pre.g633468a

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 (126) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +23 -0
  3. package/esm/cabi/async_values.js +162 -0
  4. package/esm/cabi/bulk_lists.js +198 -0
  5. package/esm/cabi/context.js +42 -0
  6. package/esm/cabi/flatten.js +145 -0
  7. package/esm/cabi/float.js +67 -0
  8. package/esm/cabi/handles.js +253 -0
  9. package/esm/cabi/layout.js +149 -0
  10. package/esm/cabi/lift.js +196 -0
  11. package/esm/cabi/load.js +146 -0
  12. package/esm/cabi/lower.js +141 -0
  13. package/esm/cabi/memory.js +182 -0
  14. package/esm/cabi/mod.js +22 -0
  15. package/esm/cabi/store.js +186 -0
  16. package/esm/cabi/strings.js +336 -0
  17. package/esm/cabi/trap.js +38 -0
  18. package/esm/cabi/types.js +264 -0
  19. package/esm/cabi/values.js +64 -0
  20. package/esm/cache/core.js +156 -0
  21. package/esm/cache/dir.js +170 -0
  22. package/esm/cache/mod.js +4 -0
  23. package/esm/cache/web.js +136 -0
  24. package/esm/digest/digest.js +332 -0
  25. package/esm/digest/mod.js +3 -0
  26. package/esm/digest/verify.js +129 -0
  27. package/esm/embedder/casing.js +56 -0
  28. package/esm/embedder/copy.js +42 -0
  29. package/esm/embedder/errors.js +26 -0
  30. package/esm/embedder/imports.js +63 -0
  31. package/esm/embedder/instantiate.js +978 -0
  32. package/esm/embedder/mod.js +40 -0
  33. package/esm/embedder/resources.js +406 -0
  34. package/esm/embedder/streams.js +770 -0
  35. package/esm/embedder/values.js +450 -0
  36. package/esm/embedder/version.js +273 -0
  37. package/esm/exec/boundary.js +1902 -0
  38. package/esm/exec/executor.js +1004 -0
  39. package/esm/exec/host_streams.js +818 -0
  40. package/esm/exec/mod.js +4 -0
  41. package/esm/intrinsics/async_builtins.js +510 -0
  42. package/esm/intrinsics/context.js +90 -0
  43. package/esm/intrinsics/errors.js +13 -0
  44. package/esm/intrinsics/fact_calls.js +865 -0
  45. package/esm/intrinsics/mod.js +564 -0
  46. package/esm/intrinsics/stream_builtins.js +578 -0
  47. package/esm/intrinsics/transcode.js +443 -0
  48. package/esm/jspi/bridge.js +579 -0
  49. package/esm/jspi/mechanics.js +89 -0
  50. package/esm/jspi/mod.js +5 -0
  51. package/esm/jspi/suspending.js +15 -0
  52. package/esm/jspi/types.js +29 -0
  53. package/esm/package.json +3 -0
  54. package/esm/plan/format.js +5 -0
  55. package/esm/plan/loader.js +657 -0
  56. package/esm/plan/mod.js +3 -0
  57. package/esm/shim/mod.js +2 -0
  58. package/esm/shim/translator.js +94 -0
  59. package/esm/task/mod.js +483 -0
  60. package/esm/task/scheduler.js +1028 -0
  61. package/esm/task/streams.js +786 -0
  62. package/esm/task/subtask.js +135 -0
  63. package/esm/task/thread.js +255 -0
  64. package/esm/task/waitable.js +144 -0
  65. package/package.json +91 -0
  66. package/types/cabi/async_values.d.ts +35 -0
  67. package/types/cabi/bulk_lists.d.ts +18 -0
  68. package/types/cabi/context.d.ts +59 -0
  69. package/types/cabi/flatten.d.ts +14 -0
  70. package/types/cabi/float.d.ts +14 -0
  71. package/types/cabi/handles.d.ts +70 -0
  72. package/types/cabi/layout.d.ts +13 -0
  73. package/types/cabi/lift.d.ts +25 -0
  74. package/types/cabi/load.d.ts +14 -0
  75. package/types/cabi/lower.d.ts +10 -0
  76. package/types/cabi/memory.d.ts +58 -0
  77. package/types/cabi/mod.d.ts +15 -0
  78. package/types/cabi/store.d.ts +12 -0
  79. package/types/cabi/strings.d.ts +23 -0
  80. package/types/cabi/trap.d.ts +11 -0
  81. package/types/cabi/types.d.ts +206 -0
  82. package/types/cabi/values.d.ts +5 -0
  83. package/types/cache/core.d.ts +97 -0
  84. package/types/cache/dir.d.ts +6 -0
  85. package/types/cache/mod.d.ts +3 -0
  86. package/types/cache/web.d.ts +10 -0
  87. package/types/digest/digest.d.ts +17 -0
  88. package/types/digest/mod.d.ts +2 -0
  89. package/types/digest/verify.d.ts +48 -0
  90. package/types/embedder/casing.d.ts +40 -0
  91. package/types/embedder/copy.d.ts +24 -0
  92. package/types/embedder/errors.d.ts +11 -0
  93. package/types/embedder/imports.d.ts +47 -0
  94. package/types/embedder/instantiate.d.ts +88 -0
  95. package/types/embedder/mod.d.ts +11 -0
  96. package/types/embedder/resources.d.ts +158 -0
  97. package/types/embedder/streams.d.ts +202 -0
  98. package/types/embedder/values.d.ts +70 -0
  99. package/types/embedder/version.d.ts +85 -0
  100. package/types/exec/boundary.d.ts +360 -0
  101. package/types/exec/executor.d.ts +125 -0
  102. package/types/exec/host_streams.d.ts +165 -0
  103. package/types/exec/mod.d.ts +3 -0
  104. package/types/intrinsics/async_builtins.d.ts +69 -0
  105. package/types/intrinsics/context.d.ts +28 -0
  106. package/types/intrinsics/errors.d.ts +5 -0
  107. package/types/intrinsics/fact_calls.d.ts +120 -0
  108. package/types/intrinsics/mod.d.ts +187 -0
  109. package/types/intrinsics/stream_builtins.d.ts +113 -0
  110. package/types/intrinsics/transcode.d.ts +21 -0
  111. package/types/jspi/bridge.d.ts +227 -0
  112. package/types/jspi/mechanics.d.ts +50 -0
  113. package/types/jspi/mod.d.ts +3 -0
  114. package/types/jspi/suspending.d.ts +1 -0
  115. package/types/jspi/types.d.ts +26 -0
  116. package/types/plan/format.d.ts +369 -0
  117. package/types/plan/loader.d.ts +113 -0
  118. package/types/plan/mod.d.ts +2 -0
  119. package/types/shim/mod.d.ts +1 -0
  120. package/types/shim/translator.d.ts +55 -0
  121. package/types/task/mod.d.ts +257 -0
  122. package/types/task/scheduler.d.ts +421 -0
  123. package/types/task/streams.d.ts +370 -0
  124. package/types/task/subtask.d.ts +96 -0
  125. package/types/task/thread.d.ts +73 -0
  126. package/types/task/waitable.d.ts +67 -0
@@ -0,0 +1,978 @@
1
+ // The conventions facade: `instantiate(artifacts, imports, opts)`.
2
+ //
3
+ // DESIGN (orchestrator ruling, C2): the facade is **runtime-driven**. Every
4
+ // camelCase name, every resource class and every import wrapper is built here,
5
+ // at instantiate time, from the loaded plan's type tables — the plan already
6
+ // carries names, kinds and function types. Bindgen emits compile-time *types*
7
+ // that cast this facade; no generated code participates, so everything works
8
+ // fully untyped.
9
+ //
10
+ // Governing contract: contracts/embedder-api.md (all sections). Secondary:
11
+ // contracts/plan-format.md for the wire shapes read here.
12
+ import { loadEnvelope, loadPlan, PlanError } from "../plan/loader.js";
13
+ import { Trap } from "../cabi/trap.js";
14
+ import { CONSTRUCTOR_SYNC_ENTRY, hostResourceType, instantiateComponent, } from "../exec/mod.js";
15
+ import { camelCase, parseLeafName, pascalCase } from "./casing.js";
16
+ import { isSuspending, suspending } from "../jspi/suspending.js";
17
+ import { Translator } from "../shim/mod.js";
18
+ import { copyCensus, isTrap, isComponentException } from "@polyengine/protocol";
19
+ import { NameCollisionError, ComponentException } from "./errors.js";
20
+ import { requiredImports } from "./imports.js";
21
+ import { hostDtorCall } from "../exec/boundary.js";
22
+ import { buildGuestResourceClass, HostResourceRegistry, invalidateWrapper, lendWrapper, makeWrapper, takeRep, } from "./resources.js";
23
+ import { BorrowScope, describe, fromHost, toHost, } from "./values.js";
24
+ import { ImportResolver } from "./version.js";
25
+ import { Future, Stream } from "./streams.js";
26
+ /** Per-element codec for a `future<T>` returned in function-result position. */
27
+ function elementCodec(element, o) {
28
+ return {
29
+ element,
30
+ where: o.where,
31
+ toHost: (v) => element === null ? undefined : toHost(v, element, o),
32
+ fromHost: (v) => element === null ? null : fromHost(v, element, o),
33
+ };
34
+ }
35
+ /**
36
+ * Reconstitute `ComponentArtifacts` from a translation ENVELOPE — the
37
+ * single-file JSON emitted by build-time translation (`tools/translate`,
38
+ * or `Translator.translateRaw`), carrying the plan and the FACT adapter
39
+ * modules. The production deploy set is `component.wasm` + its envelope +
40
+ * the runtime: no translator ships (embedder-api.md amendment A4).
41
+ *
42
+ * Pure and fetch-agnostic: acquire the two blobs however the platform
43
+ * likes (HTTP, fs, bundler asset) and hand them over. The envelope embeds
44
+ * the component's sha-256, which `instantiate` verifies — a mismatched
45
+ * pair fails loudly at instantiation, never subtly at runtime.
46
+ */
47
+ export function artifactsFromEnvelope(envelopeJson, componentBytes) {
48
+ const { wire, adapters } = loadEnvelope(envelopeJson);
49
+ return { plan: wire, componentBytes, adapters };
50
+ }
51
+ /**
52
+ * Normalize either accepted input form to `ComponentArtifacts` — i.e. make
53
+ * the PLAN available without instantiating anything. Exported because the
54
+ * world-digest handshake (contracts/digest.md) must complete before any
55
+ * guest code runs: generated `instantiate` wrappers call this, verify the
56
+ * plan, and only then delegate to `instantiate` below.
57
+ */
58
+ export async function resolveArtifacts(src) {
59
+ if ("plan" in src)
60
+ return src;
61
+ const translator = src.translator instanceof Translator
62
+ ? src.translator
63
+ : await Translator.create(src.translator);
64
+ const { plan, adapters } = translator.translate(src.componentBytes);
65
+ return { plan, componentBytes: src.componentBytes, adapters };
66
+ }
67
+ /**
68
+ * Instantiate a component behind the embedder conventions.
69
+ *
70
+ * `imports` is the canonical nested record of
71
+ * contracts/embedder-api.md §"Module wiring and instantiation": keys are
72
+ * verbatim WIT interface ids (version included) or world-level camelCase
73
+ * names; interface-id keys additionally participate in compatibility-track
74
+ * resolution (see `version.ts`).
75
+ */
76
+ export async function instantiate(source, imports = {}, opts = {}) {
77
+ const artifacts = await resolveArtifacts(source);
78
+ const facade = new Facade(artifacts, imports);
79
+ const handle = await instantiateComponent({
80
+ plan: artifacts.plan,
81
+ componentBytes: artifacts.componentBytes,
82
+ adapters: artifacts.adapters,
83
+ imports: facade.rawImports,
84
+ jspi: opts.jspi,
85
+ verifyHash: opts.verifyHash,
86
+ // THE ordering fix: the facade converted this plan in its constructor and
87
+ // wired its import wrappers against those very `ResourceTypeInfo` tokens.
88
+ // Host imports fire DURING instantiation (a core module's `start`
89
+ // function runs inside `runInitializers`), so the facade cannot wait for
90
+ // the handle to learn its own types.
91
+ loadedPlan: facade.loaded,
92
+ });
93
+ facade.bind(handle);
94
+ const instance = {
95
+ exports: facade.buildExports(handle),
96
+ handle,
97
+ imports: facade.leaves,
98
+ };
99
+ Object.defineProperty(instance, INTERNAL_HOST_REGISTRIES, {
100
+ value: facade.hostRegistries,
101
+ enumerable: false,
102
+ });
103
+ return instance;
104
+ }
105
+ /** Alias matching the C2 dispatch's spelling. */
106
+ export const instantiateEmbedder = instantiate;
107
+ /**
108
+ * Symbol-keyed, deliberately NOT re-exported from `mod.ts`: the
109
+ * host-resource registries of an instance, by `ResourceIndex`. Diagnostics and
110
+ * white-box tests only — it is not part of the embedder API surface and no
111
+ * generated code may depend on it.
112
+ */
113
+ export const INTERNAL_HOST_REGISTRIES = Symbol("polyengine.embedder.hostRegistries");
114
+ class Facade {
115
+ artifacts;
116
+ leaves;
117
+ rawImports = {};
118
+ #resolver;
119
+ #bindings = new Map();
120
+ /** ResourceTypeInfo identity -> ResourceIndex (one index, many tokens). */
121
+ #tokenIndex = new Map();
122
+ /**
123
+ * The converted plan — owned by the facade and handed to the executor, so
124
+ * both sides share one set of per-instantiation resource identity tokens.
125
+ * Available from construction, which is what makes import wrappers usable
126
+ * for the whole of instantiation.
127
+ */
128
+ loaded;
129
+ #bridge;
130
+ /**
131
+ * Releases for reps minted while lowering the CURRENT call's arguments.
132
+ * Argument lowering is synchronous and uninterrupted (no `await` between
133
+ * `#lowerScope = […]` and the reset), so a single slot is race-free even
134
+ * with concurrent export calls in flight.
135
+ */
136
+ #lowerScope = null;
137
+ /** ResourceIndex -> registry, for diagnostics (see INTERNAL_HOST_REGISTRIES). */
138
+ hostRegistries = new Map();
139
+ /** True once `buildExports` has run: guest resource classes then exist. */
140
+ #exportsBuilt = false;
141
+ constructor(artifacts, providers) {
142
+ this.artifacts = artifacts;
143
+ this.#resolver = new ImportResolver(providers);
144
+ this.loaded = loadPlan(artifacts.plan);
145
+ // `ResourceTypeInfo` identity -> `ResourceIndex`. Both halves are static
146
+ // (the tokens are ours; `resourceTables` is wire data), so this map is
147
+ // complete before instantiation starts — a host import that fires from a
148
+ // guest `start` function can resolve resource types normally.
149
+ //
150
+ // One resource TYPE can be reached through several resource TABLES
151
+ // (plan-format.md C2 amendment #1: a type export's index is a table
152
+ // index, and the executor sets impl/dtor on every table whose `resource`
153
+ // matches), hence index-keyed bindings with tokens as aliases.
154
+ artifacts.plan.resourceTables.forEach((table, i) => {
155
+ if (table.kind !== "concrete")
156
+ return;
157
+ const token = this.loaded.resourceTokens[i];
158
+ if (token !== undefined)
159
+ this.#tokenIndex.set(token, table.resource);
160
+ });
161
+ this.leaves = requiredImports(this.loaded);
162
+ // A component that imports a resource TYPE cannot be wired without
163
+ // `plan.importedResources`: that table is the only thing mapping the
164
+ // import back to a `ResourceIndex` (plan-format.md v0.1 amendment #2 /
165
+ // v0.2). Without it every own/borrow of that type would fail late, deep
166
+ // inside a call, as an unattributable `InvalidHandleError`.
167
+ const resourceLeaves = this.leaves.filter((l) => l.kind === "resource");
168
+ if (resourceLeaves.length > 0 &&
169
+ (artifacts.plan.importedResources ?? []).length === 0) {
170
+ throw new PlanError(`this component imports the resource type(s) ` +
171
+ `${resourceLeaves.map((l) => `'${l.leaf}'`).join(", ")}, but the ` +
172
+ `plan carries no \`importedResources\` table, so they cannot be ` +
173
+ `bound to a ResourceIndex (contracts/plan-format.md v0.2). ` +
174
+ `Re-translate with a shim that emits it.`);
175
+ }
176
+ this.#bridge = this.#makeBridge();
177
+ this.#buildRawImports();
178
+ this.#bindHostResources();
179
+ }
180
+ // -- resource-type identity ------------------------------------------------
181
+ /**
182
+ * Consistency check after instantiation.
183
+ *
184
+ * The facade no longer *learns* anything here — it handed its own
185
+ * `LoadedPlan` to the executor precisely so that nothing about types or
186
+ * resource identity depends on instantiation having finished. All this does
187
+ * is assert the executor did not silently re-load (which would give it a
188
+ * second, disjoint set of `ResourceTypeInfo` tokens and make every
189
+ * `own`/`borrow` unresolvable).
190
+ */
191
+ bind(handle) {
192
+ if (handle.loadedPlan !== this.loaded) {
193
+ throw new PlanError("the executor instantiated from a different LoadedPlan than the " +
194
+ "facade built its import wrappers from; resource identity tokens " +
195
+ "would not match");
196
+ }
197
+ }
198
+ #indexOf(rt) {
199
+ const i = this.#tokenIndex.get(rt);
200
+ if (i === undefined) {
201
+ throw new PlanError("resource type is not bound to any resource table in this plan");
202
+ }
203
+ return i;
204
+ }
205
+ #binding(rt) {
206
+ const index = this.#indexOf(rt);
207
+ let b = this.#bindings.get(index);
208
+ if (b === undefined) {
209
+ // A GUEST-implemented resource. Unlike host-implemented ones (bound at
210
+ // construction from static plan data), a guest resource's class is
211
+ // assembled from the component's own lifted `[constructor]`/`[method]`
212
+ // exports, which do not exist until instantiation has finished. If a
213
+ // guest `start` function hands one to a host import, say so precisely
214
+ // rather than surfacing a half-built wrapper.
215
+ if (!this.#exportsBuilt) {
216
+ throw new PlanError(`a guest-implemented resource (ResourceIndex ${index}) crossed the ` +
217
+ `boundary before instantiation finished — a guest \`start\` ` +
218
+ `function passed an own/borrow handle to a host import. Its class ` +
219
+ `is assembled from the component's own lifted exports, which do ` +
220
+ `not exist yet. Host-implemented resources are unaffected. If a ` +
221
+ `real component needs this, the class must be built lazily from ` +
222
+ `the plan's export table instead of the runtime's export surface.`);
223
+ }
224
+ // Post-instantiation: a guest resource with no exported type and no
225
+ // exported leaves. Still a valid handle, just anonymous.
226
+ b = { kind: "guest", name: `resource-${index}` };
227
+ this.#bindings.set(index, b);
228
+ }
229
+ return b;
230
+ }
231
+ // deno-lint-ignore no-explicit-any
232
+ #guestClass(b) {
233
+ b.cls ??= buildGuestResourceClass({ name: b.name, ctor: null, ctorParams: null, methods: [], statics: [] },
234
+ // The rt is supplied per wrapper, so an anonymous class needs none here.
235
+ { impl: null, dtor: null }, () => Promise.reject(new TypeError("no methods")), () => []);
236
+ return b.cls;
237
+ }
238
+ /**
239
+ * Bind host-implemented resource types to their `ResourceIndex`.
240
+ *
241
+ * Everything this needs is static wire data (`plan.importedResources`, whose
242
+ * entries are back-references into `plan.imports`), so it runs at
243
+ * construction — before instantiation, and therefore before a guest `start`
244
+ * function can call an import that carries an `own`/`borrow` of one.
245
+ * Imported resources occupy `ResourceIndex` 0..n-1 in `importedResources`
246
+ * order (plan-format.md v0.1 amendment #2 / v0.2).
247
+ */
248
+ #bindHostResources() {
249
+ const importedResources = this.artifacts.plan.importedResources ?? [];
250
+ for (const p of this.#pendingHostResources) {
251
+ const at = importedResources.findIndex((ir) => ir.import === p.importIndex);
252
+ if (at < 0)
253
+ continue;
254
+ this.#bindings.set(at, {
255
+ kind: "host",
256
+ name: this.leaves[p.importIndex].leaf,
257
+ registry: p.registry,
258
+ cls: p.cls,
259
+ });
260
+ this.hostRegistries.set(at, p.registry);
261
+ }
262
+ }
263
+ // -- the value bridge ------------------------------------------------------
264
+ #makeBridge() {
265
+ const self = this;
266
+ return {
267
+ liftOwn(rep, t) {
268
+ const b = self.#binding(t.rt);
269
+ // Host-implemented R: "the host's own instance back; the guest's
270
+ // handle is gone; no dispose call" (contract 2x4 table).
271
+ if (b.kind === "host")
272
+ return b.registry.release(rep);
273
+ return makeWrapper(self.#guestClass(b), rep, t.rt, true);
274
+ },
275
+ liftBorrow(rep, t, scope) {
276
+ const b = self.#binding(t.rt);
277
+ // Host-implemented R: "the host's own instance; borrow scoping is
278
+ // guest-side bookkeeping" — the mapping is kept.
279
+ if (b.kind === "host")
280
+ return b.registry.lookup(rep);
281
+ const w = makeWrapper(self.#guestClass(b), rep, t.rt, false);
282
+ scope.add(() => invalidateWrapper(w));
283
+ return w;
284
+ },
285
+ lowerOwn(v, t) {
286
+ const b = self.#binding(t.rt);
287
+ if (b.kind === "host")
288
+ return b.registry.repFor(v);
289
+ return takeRep(v, true, `own<${b.name}>`);
290
+ },
291
+ lowerBorrow(v, t) {
292
+ const b = self.#binding(t.rt);
293
+ if (b.kind === "host") {
294
+ // Contract 2x4 table, bottom-right: "a never-registered instance
295
+ // gets a rep allocated **for the call's duration**". A rep minted
296
+ // here is call-scoped, so it is released when the call returns —
297
+ // otherwise it would sit in the registry's STRONG rep->instance map
298
+ // forever, since a guest dropping a borrow handle runs no dtor.
299
+ const known = b.registry.hasInstance(v);
300
+ const rep = b.registry.repFor(v);
301
+ if (!known) {
302
+ self.#lowerScope?.push(() => b.registry.releaseIfPresent(rep));
303
+ }
304
+ return rep;
305
+ }
306
+ // Host `own` wrapper lowered as `borrow<R>` (#86): record the lend
307
+ // for the duration of this call, so a `drop()` or a GC finalization
308
+ // in the window cannot destroy a rep the guest still borrows.
309
+ // definitions.py `lift_borrow` -> `Subtask.add_lender` (line 890);
310
+ // `#lowerScope` is released where that subtask delivers its
311
+ // resolution, i.e. when the call ends.
312
+ const rep = takeRep(v, false, `borrow<${b.name}>`);
313
+ const release = lendWrapper(v);
314
+ if (self.#lowerScope === null) {
315
+ // No enclosing lowering scope (a raw/one-off lowering): the lend
316
+ // has no observable window, so it must not be left dangling.
317
+ release();
318
+ }
319
+ else {
320
+ self.#lowerScope.push(release);
321
+ }
322
+ return rep;
323
+ },
324
+ dropOwn(rep, t) {
325
+ // A13: a lowered `own` the guest will never take (an un-taken
326
+ // stream element). Destroy it exactly as a guest-side drop would:
327
+ // host-implemented R runs the instance's [Symbol.dispose] through
328
+ // the registry; guest-implemented R runs the guest dtor via the
329
+ // gated path (a host-initiated drop, `caller = None`).
330
+ const b = self.#binding(t.rt);
331
+ if (b.kind === "host") {
332
+ b.registry.dtor(rep);
333
+ return;
334
+ }
335
+ hostDtorCall(t.rt, rep);
336
+ },
337
+ };
338
+ }
339
+ #opts(where) {
340
+ return { bridge: this.#bridge, where };
341
+ }
342
+ #funcType(index, what) {
343
+ const loaded = this.loaded;
344
+ if (index === undefined)
345
+ throw new PlanError(`${what}: no type index`);
346
+ const t = loaded.types[index];
347
+ if (t === undefined || t.kind !== "func") {
348
+ throw new PlanError(`${what}: type ${index} is not a function type`);
349
+ }
350
+ return t.funcType;
351
+ }
352
+ // -- imports ---------------------------------------------------------------
353
+ #buildRawImports() {
354
+ // Group by the record key so an instance import lands as one nested object.
355
+ this.leaves.forEach((leaf, importIndex) => {
356
+ const provider = this.#provider(leaf);
357
+ const target = leaf.path.length === 0
358
+ ? null
359
+ : nest(this.rawImports, leaf.interfaceId, leaf.path.slice(0, -1));
360
+ const value = this.#wrapLeaf(leaf, importIndex, provider);
361
+ if (target === null)
362
+ this.rawImports[leaf.interfaceId] = value;
363
+ else
364
+ target[leaf.path[leaf.path.length - 1]] = value;
365
+ });
366
+ }
367
+ /** Resolve the container object a leaf's implementation is read from. */
368
+ #provider(leaf) {
369
+ // A world-level MEMBER leaf (`[method]ticket.value` with no containing
370
+ // interface) dispatches on the resource's class, which is registered
371
+ // under the resource's own name — the mangled leaf name is never a
372
+ // record key. Interface-level members find their class inside the
373
+ // interface record via the normal path walk below.
374
+ if (leaf.path.length === 0 && leaf.member.form !== "plain") {
375
+ const r = leaf.member.resource;
376
+ const hit = this.#resolver.resolve(r) ??
377
+ this.#resolver.resolve(camelCase(r));
378
+ if (hit === undefined) {
379
+ throw new PlanError(`host import '${label(leaf)}' not provided: the component ` +
380
+ `imports the world-level resource '${r}'; provide its class ` +
381
+ `under the key '${camelCase(r)}' (registered: ` +
382
+ `${this.#resolver.keys().join(", ") || "<none>"})`);
383
+ }
384
+ return hit.value;
385
+ }
386
+ const hit = this.#resolver.resolve(leaf.interfaceId) ??
387
+ (leaf.path.length === 0
388
+ ? this.#resolver.resolve(camelCase(leaf.interfaceId))
389
+ : undefined);
390
+ if (hit === undefined) {
391
+ throw new PlanError(`host import '${label(leaf)}' not provided (no key ` +
392
+ `'${leaf.interfaceId}' in imports; registered: ` +
393
+ `${this.#resolver.keys().join(", ") || "<none>"})`);
394
+ }
395
+ let value = hit.value;
396
+ // Walk everything but the final segment; the leaf itself is read by
397
+ // `#wrapLeaf`, which knows how to decode a mangled name.
398
+ for (const seg of leaf.path.slice(0, -1)) {
399
+ if (value === null || typeof value !== "object") {
400
+ throw new PlanError(`host import '${label(leaf)}': '${seg}' is not reachable ` +
401
+ `(${describe(value)})`);
402
+ }
403
+ value = value[seg];
404
+ }
405
+ return value;
406
+ }
407
+ #wrapLeaf(leaf, importIndex, provider) {
408
+ if (leaf.kind === "resource") {
409
+ return this.#wrapResourceType(leaf, importIndex, provider);
410
+ }
411
+ if (leaf.kind !== "func") {
412
+ // `instance` leaves never appear as plan imports in their own right
413
+ // (the plan flattens them into paths); anything else is out of scope.
414
+ throw new PlanError(`host import '${label(leaf)}': unsupported import kind '${leaf.kind}'`);
415
+ }
416
+ const dispatch = this.#dispatcher(leaf, provider);
417
+ // The function type is resolved LAZILY, on first call. It must come from
418
+ // the *executor's* loaded plan: the `own`/`borrow` types in it carry the
419
+ // per-instantiation `ResourceTypeInfo` identity tokens the bridge keys on,
420
+ // and those objects do not exist until `instantiateComponent` has run —
421
+ // which is after this wrapper has to be handed to it.
422
+ let impl = null;
423
+ const wrapper = (...raw) => {
424
+ if (impl === null) {
425
+ const ft = this.#funcType(this.artifacts.plan.imports[importIndex].type, `import '${label(leaf)}'`);
426
+ impl = this.#wrapImportFn(leaf, ft, dispatch);
427
+ }
428
+ return impl(...raw);
429
+ };
430
+ // A1 brand relay, layer 2 of 2 (see #dispatcher): the executor reads the
431
+ // brand off this wrapper, which is what lands in its hostImports record.
432
+ return isSuspending(dispatch) ? suspending(wrapper) : wrapper;
433
+ }
434
+ /** A host-implemented resource type: register the class, own the mapping. */
435
+ #wrapResourceType(leaf, importIndex, provider) {
436
+ // `#provider` already walked every path segment but the last, so a
437
+ // path-bearing resource import reads its class off `provider`; a
438
+ // world-level one IS `provider`.
439
+ const cls = leaf.path.length === 0
440
+ ? provider
441
+ : pick(provider, [], [pascalCase(leaf.leaf), leaf.leaf]);
442
+ if (cls === undefined) {
443
+ throw new PlanError(`host import '${label(leaf)}': the component imports the resource ` +
444
+ `type '${leaf.leaf}'; provide the implementing class as ` +
445
+ `'${pascalCase(leaf.leaf)}'`);
446
+ }
447
+ const registry = new HostResourceRegistry(pascalCase(leaf.leaf));
448
+ this.#pendingHostResources.push({ importIndex, registry, cls });
449
+ return hostResourceType({
450
+ name: leaf.leaf,
451
+ // The guest dropped its last own handle: run the destructor, which for
452
+ // a host-implemented resource is `instance[Symbol.dispose]?.()`.
453
+ dtor: (rep) => registry.dtor(rep),
454
+ });
455
+ }
456
+ #pendingHostResources = [];
457
+ /** The JS call a lifted import leaf dispatches to. */
458
+ #dispatcher(leaf, provider) {
459
+ const m = leaf.member;
460
+ if (m.form === "plain") {
461
+ const fn = leaf.path.length === 0
462
+ ? provider
463
+ : pick(provider, [], [camelCase(m.name), m.name]);
464
+ if (typeof fn !== "function") {
465
+ throw new PlanError(`host import '${label(leaf)}' missing or not a function (got ` +
466
+ `${describe(fn)}); expected '${camelCase(m.name)}'`);
467
+ }
468
+ // A1: the `suspending()` brand rides the dispatch closure so #wrapLeaf
469
+ // can relay it onto the value the executor actually receives.
470
+ //
471
+ // A2 receiver rule: an interface member is invoked with its containing
472
+ // object as receiver (matching the static arm's `apply(cls)`), so a
473
+ // class INSTANCE is a fully supported spelling of an interface
474
+ // provider — methods reading instance state work. A world-level bare
475
+ // import has no containing object and stays unbound. (Previously the
476
+ // plain arm called extracted functions unbound: a class-instance
477
+ // provider type-checked, worked while stateless, and broke with
478
+ // `this === undefined` the moment a method touched state — the silent
479
+ // liberal-acceptance failure the contract forbids.)
480
+ const receiver = leaf.path.length === 0 ? undefined : provider;
481
+ const dispatch = (args) => fn.apply(receiver, args);
482
+ return isSuspending(fn) ? suspending(dispatch) : dispatch;
483
+ }
484
+ const clsName = pascalCase(m.resource);
485
+ // World-level member leaves resolved the class itself (`#provider`);
486
+ // interface members read it out of the interface record.
487
+ const cls = leaf.path.length === 0
488
+ ? provider
489
+ : pick(provider, [], [clsName, m.resource]);
490
+ if (cls === undefined) {
491
+ throw new PlanError(`host import '${label(leaf)}': no class '${clsName}' provided`);
492
+ }
493
+ switch (m.form) {
494
+ case "constructor":
495
+ // Never markable: guest-driven construction of a host resource is
496
+ // synchronous by the C2 amendment, and stage-3 reserves no
497
+ // constructor-decorator position.
498
+ // deno-lint-ignore no-explicit-any
499
+ return (args) => new cls(...args);
500
+ case "method": {
501
+ // A2: the brand authority for an instance method is the CLASS
502
+ // PROTOTYPE, read at wrap time — the Suspending-wrap decision is
503
+ // per-declaration and taken at instantiation, before any instance
504
+ // exists. Instance-level method overrides do not change
505
+ // suspendability (marking follows the WIT declaration, not the
506
+ // object); the per-call lookup below still dispatches to the
507
+ // override's BODY as before.
508
+ //
509
+ // The probe must not INVOKE accessors: a platform getter (e.g.
510
+ // `URLSearchParams.prototype.size`) brand-checks its receiver, and a
511
+ // raw `prototype[member]` read runs it with `this` = the prototype —
512
+ // an engine TypeError at instantiation, even for guests that never
513
+ // call the member. Only a data-property function can carry the A2
514
+ // mark (stage-3 method decorators install data properties), so an
515
+ // accessor-backed member yields no wrap-time function here and stays
516
+ // a call-time concern for the per-call lookup below.
517
+ const protoFn = dataMember(cls?.prototype, camelCase(m.member));
518
+ const dispatch = (args) => {
519
+ const [self, ...rest] = args;
520
+ const fn = self?.[camelCase(m.member)];
521
+ if (typeof fn !== "function") {
522
+ throw new Trap(`host import '${label(leaf)}': the ${clsName} instance has no ` +
523
+ `method '${camelCase(m.member)}'`);
524
+ }
525
+ return fn.apply(self, rest);
526
+ };
527
+ return isSuspending(protoFn) ? suspending(dispatch) : dispatch;
528
+ }
529
+ case "static": {
530
+ const fn = cls[camelCase(m.member)];
531
+ if (typeof fn !== "function") {
532
+ throw new PlanError(`host import '${label(leaf)}': ${clsName} has no static ` +
533
+ `'${camelCase(m.member)}'`);
534
+ }
535
+ // A2: a static's brand sits on the function itself (a stage-3
536
+ // static-method decorator marks the function value), readable here
537
+ // at wrap time.
538
+ const dispatch = (args) => fn.apply(cls, args);
539
+ return isSuspending(fn) ? suspending(dispatch) : dispatch;
540
+ }
541
+ }
542
+ }
543
+ /**
544
+ * The raw (definitions.py-shaped) function the executor lowers, wrapping a
545
+ * conventions-shaped host implementation.
546
+ *
547
+ * Error model (contract §"Error model"), the inversion of jco's convention:
548
+ * * a returned value is the ok side;
549
+ * * `throw new ComponentException(payload)` is the err side of a `result<T, E>`;
550
+ * * a `Trap` passes through unchanged;
551
+ * * **any other throw is a host bug and becomes a trap naming the import**
552
+ * — never a guest-visible err. This is what makes the consumers'
553
+ * defensive `platformCall`-style wrappers unnecessary by construction.
554
+ */
555
+ #wrapImportFn(leaf, ft, dispatch) {
556
+ const where = `import '${label(leaf)}'`;
557
+ const o = this.#opts(where);
558
+ const resultType = ft.results.length === 0 ? null : ft.results[0];
559
+ const isResult = resultType !== null && resultType.kind === "result";
560
+ const ok = (v) => {
561
+ if (resultType === null)
562
+ return undefined;
563
+ if (isResult) {
564
+ const rt = resultType;
565
+ return { ok: rt.ok === null ? null : fromHost(v, rt.ok, o) };
566
+ }
567
+ return fromHost(v, resultType, o);
568
+ };
569
+ const fail = (e, args) => {
570
+ // Brand, not class (amendment A9): a `ComponentException` thrown by a host module
571
+ // that resolved a DIFFERENT runtime copy — or hand-rolled with the
572
+ // registry symbol — is the same value here (issue #83).
573
+ if (isComponentException(e) && isResult) {
574
+ const rt = resultType;
575
+ return {
576
+ error: rt.error === null ? null : fromHost(e.payload, rt.error, o),
577
+ };
578
+ }
579
+ // Every remaining branch traps the component. The import's lifted
580
+ // stream/future arguments were transferred to the host when the params
581
+ // were converted (the guest's ends are gone), and a trapping import is
582
+ // a declared host bug — nothing owns them anymore, so drop them here:
583
+ // a peer parked on one (a host writer feeding the stream this import
584
+ // just received, the #66 E2 shape) settles with the truthful "reader
585
+ // went away" instead of hanging forever. The err-VALUE branch above
586
+ // deliberately does NOT do this: a fallible import returning err is a
587
+ // normal outcome whose implementation may retain the handles.
588
+ releaseAsyncArgs(args);
589
+ if (isTrap(e))
590
+ throw e;
591
+ if (isComponentException(e)) {
592
+ throw new Trap(`${where} threw a ComponentException, but its WIT type has no err side; ` +
593
+ `only a fallible import may signal an error value`);
594
+ }
595
+ // The #83 signature: in a graph with several copies, an UNBRANDED throw
596
+ // is usually a pre-A9 copy's `ComponentException` (its brand rode class identity,
597
+ // which does not survive the copy boundary). Say so rather than leaving
598
+ // the latent puzzle that motivated amendment A9.
599
+ const census = copyCensus();
600
+ throw new Trap(`${where} threw ${describeThrow(e)}. An unbranded throw from a host ` +
601
+ `import is a host bug and becomes a trap: signal a WIT error with ` +
602
+ `\`throw new ComponentException(payload)\`.` +
603
+ (census === ""
604
+ ? ""
605
+ : ` (${census} — an error carrying no polyengine brand in a ` +
606
+ `multi-copy graph usually means a pre-A9 runtime copy threw ` +
607
+ `it, issue #83.)`));
608
+ };
609
+ return (...raw) => {
610
+ const scope = new BorrowScope();
611
+ const args = ft.params.map((p, i) => toHost(raw[i], p, o, scope));
612
+ let out;
613
+ try {
614
+ out = dispatch(args);
615
+ }
616
+ catch (e) {
617
+ scope.end();
618
+ return fail(e, args);
619
+ }
620
+ if (isThenable(out)) {
621
+ // Amendment A12: when the WIT result type is `future<T>`, a thenable
622
+ // return IS the future source ("for `future<T>`, a `Promise<T>` or
623
+ // `Future<T>`" — §"Streams and futures"), not the call's async
624
+ // completion. The import completes immediately with the lowered
625
+ // future; the producer settles it on its own schedule. Without this,
626
+ // the natural spelling of the wasi:sockets 0.3 TCP `send` shape —
627
+ // `func(data: stream<u8>) -> future<result>`, an async method whose
628
+ // promise resolves when transmission completes — would park the
629
+ // call, and a future whose settlement depends on post-return guest
630
+ // action (the guest writes `data` AFTER `send` returns) livelocks.
631
+ // This branch also covers a returned `Future` handle, which is a
632
+ // PromiseLike and would otherwise be adopted and mis-lowered.
633
+ if (resultType !== null && resultType.kind === "future") {
634
+ scope.end();
635
+ return ok(out);
636
+ }
637
+ return out.then((v) => {
638
+ scope.end();
639
+ return ok(v);
640
+ }, (e) => {
641
+ scope.end();
642
+ return fail(e, args);
643
+ });
644
+ }
645
+ scope.end();
646
+ return ok(out);
647
+ };
648
+ }
649
+ // -- exports ---------------------------------------------------------------
650
+ // deno-lint-ignore no-explicit-any
651
+ buildExports(handle) {
652
+ this.#exportsBuilt = true;
653
+ // deno-lint-ignore no-explicit-any
654
+ const out = {};
655
+ const worldLeaves = [];
656
+ for (const exp of this.artifacts.plan.exports) {
657
+ if (exp.kind === "instance") {
658
+ out[exp.name] = this.#buildInterface(exp.name, exp.exports, handle.exports[exp.name]);
659
+ }
660
+ else {
661
+ worldLeaves.push(exp);
662
+ }
663
+ }
664
+ if (worldLeaves.length > 0) {
665
+ Object.assign(out, this.#buildInterface("", worldLeaves, handle.exports));
666
+ }
667
+ return out;
668
+ }
669
+ // deno-lint-ignore no-explicit-any
670
+ #buildInterface(id, exps, raw) {
671
+ // deno-lint-ignore no-explicit-any
672
+ const obj = {};
673
+ /** jsName -> the WIT leaf that claimed it (camelCase collision guard). */
674
+ const claimed = new Map();
675
+ const claim = (js, leaf) => {
676
+ const held = claimed.get(js);
677
+ if (held !== undefined) {
678
+ throw new NameCollisionError(`export '${id || "<world>"}': the leaves '${held}' and '${leaf}' ` +
679
+ `both map to the JS name '${js}'. Rename one in the WIT; the ` +
680
+ `conventions layer will not guess which one wins.`);
681
+ }
682
+ claimed.set(js, leaf);
683
+ return js;
684
+ };
685
+ const specs = new Map();
686
+ const specRt = new Map();
687
+ const spec = (name) => {
688
+ let s = specs.get(name);
689
+ if (s === undefined) {
690
+ s = { name, ctor: null, ctorParams: null, methods: [], statics: [] };
691
+ specs.set(name, s);
692
+ }
693
+ return s;
694
+ };
695
+ for (const exp of exps) {
696
+ if (exp.kind === "type") {
697
+ // A `resource` type export names the class; the ResourceIndex comes
698
+ // from the resource TABLE it points at (the wire field is a table
699
+ // index, like `own`/`borrow`).
700
+ if (exp.type.kind === "resource") {
701
+ const token = this.loaded.resourceTokens[exp.type.resource];
702
+ if (token !== undefined && this.#tokenIndex.has(token)) {
703
+ const index = this.#tokenIndex.get(token);
704
+ const held = this.#bindings.get(index);
705
+ if (held === undefined) {
706
+ this.#bindings.set(index, { kind: "guest", name: exp.name });
707
+ }
708
+ else if (held.kind === "guest") {
709
+ held.name = exp.name;
710
+ }
711
+ }
712
+ }
713
+ continue;
714
+ }
715
+ if (exp.kind === "module") {
716
+ // Not WIT-expressible, digest-excluded (plan-format.md v4 amendment
717
+ // 2): the WIT-shaped facade skips it, the type-export precedent. The
718
+ // raw executor export surface still carries the compiled module.
719
+ continue;
720
+ }
721
+ if (exp.kind === "instance") {
722
+ // The plan flattens the world's instance exports at the top level; a
723
+ // nested one would need a nested facade, which nothing produces today.
724
+ // Refuse rather than silently drop the whole sub-interface.
725
+ throw new PlanError(`export '${id || "<world>"}/${exp.name}': nested instance exports ` +
726
+ `are not surfaced by the conventions layer (only one level of ` +
727
+ `interface nesting exists in plan v2)`);
728
+ }
729
+ if (exp.kind !== "lifted-func") {
730
+ throw new PlanError(`export '${id || "<world>"}/${exp.name}': ` +
731
+ `unsupported export kind ` +
732
+ `'${exp.kind}'`);
733
+ }
734
+ const fn = raw[exp.name];
735
+ if (typeof fn !== "function") {
736
+ throw new PlanError(`export '${id || "<world>"}/${exp.name}': the runtime produced no ` +
737
+ `callable for this lifted function`);
738
+ }
739
+ const ft = this.#funcType(exp.type, `export '${id}/${exp.name}'`);
740
+ const member = parseLeafName(exp.name);
741
+ const where = id === "" ? exp.name : `${id}#${exp.name}`;
742
+ switch (member.form) {
743
+ case "plain":
744
+ obj[claim(camelCase(member.name), member.name)] = this
745
+ .#wrapExportFn(fn, ft, where);
746
+ break;
747
+ case "constructor": {
748
+ const s = spec(member.resource);
749
+ // Prefer the plain-entered variant in jspi mode: the JS `new`
750
+ // cannot await the Promise a promising-wrapped entry returns
751
+ // (exec/boundary.ts CONSTRUCTOR_SYNC_ENTRY).
752
+ s.ctor = (fn[CONSTRUCTOR_SYNC_ENTRY] ?? fn);
753
+ s.ctorParams = ft.params;
754
+ rtOf(ft.results[0], specRt, member.resource);
755
+ break;
756
+ }
757
+ case "method": {
758
+ spec(member.resource).methods.push({
759
+ member: member.member,
760
+ raw: fn,
761
+ params: ft.params,
762
+ results: ft.results,
763
+ });
764
+ rtOf(ft.params[0], specRt, member.resource);
765
+ break;
766
+ }
767
+ case "static": {
768
+ spec(member.resource).statics.push({
769
+ member: member.member,
770
+ raw: fn,
771
+ params: ft.params,
772
+ results: ft.results,
773
+ });
774
+ break;
775
+ }
776
+ }
777
+ }
778
+ for (const [name, s] of specs) {
779
+ const rt = specRt.get(name);
780
+ if (rt === undefined) {
781
+ throw new PlanError(`export '${id}': resource '${name}' has leaves but no own/borrow ` +
782
+ `type to identify it by`);
783
+ }
784
+ const cls = buildGuestResourceClass(s, rt, (fn, params, results, where, args) => this.#wrapExportFn(fn, { params, results }, where)(...args), (args, params, where) => args.map((a, i) => fromHost(a, params[i], this.#opts(where))));
785
+ obj[claim(pascalCase(name), name)] = cls;
786
+ const index = this.#tokenIndex.get(rt);
787
+ if (index !== undefined) {
788
+ this.#bindings.set(index, { kind: "guest", name, cls });
789
+ }
790
+ }
791
+ return obj;
792
+ }
793
+ /**
794
+ * Lower a call's arguments, collecting the releases for anything that was
795
+ * allocated *for the duration of this call* (see `lowerBorrow`).
796
+ *
797
+ * The collection window is the synchronous argument-lowering phase only —
798
+ * `#lowerScope` is set and cleared with no `await` in between — so a single
799
+ * slot is correct even with concurrent export calls in flight.
800
+ */
801
+ #lowerParams(params, args, o) {
802
+ const scope = [];
803
+ const outer = this.#lowerScope;
804
+ this.#lowerScope = scope;
805
+ let lowered;
806
+ try {
807
+ lowered = params.map((p, i) => fromHost(args[i], p, o));
808
+ }
809
+ catch (e) {
810
+ for (const r of scope)
811
+ r();
812
+ throw e;
813
+ }
814
+ finally {
815
+ this.#lowerScope = outer;
816
+ }
817
+ let released = false;
818
+ return {
819
+ lowered,
820
+ release: () => {
821
+ if (released)
822
+ return;
823
+ released = true;
824
+ for (const r of scope)
825
+ r();
826
+ },
827
+ };
828
+ }
829
+ /**
830
+ * Wrap one lifted export.
831
+ *
832
+ * Uniformly Promise-shaped (contract §"Functions and async"): a sync
833
+ * completion resolves immediately, so there is one calling convention.
834
+ * A `result<T, E>` in *function-result* position resolves `T` or rejects
835
+ * `ComponentException<E>`; a result nested inside a value is plain `{kind, value}` data
836
+ * and never throws.
837
+ */
838
+ #wrapExportFn(fn, ft, where) {
839
+ const o = this.#opts(where);
840
+ const resultType = ft.results.length === 0 ? null : ft.results[0];
841
+ if (resultType !== null && resultType.kind === "future") {
842
+ // See `Future.deferred`: a `future<T>` result cannot be delivered
843
+ // *through* a Promise, because promise resolution adopts thenables and
844
+ // `Future<T>` is one. The handle is returned eagerly instead; it is
845
+ // PromiseLike, so `await` still yields `T`.
846
+ const element = resultType.element;
847
+ return (...args) => {
848
+ // Advisory 9: the generic branch checks arity; so must this one.
849
+ if (args.length !== ft.params.length) {
850
+ throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ` +
851
+ `${args.length}`);
852
+ }
853
+ const { lowered, release } = this.#lowerParams(ft.params, args, o);
854
+ let pending;
855
+ try {
856
+ pending = Promise.resolve(fn(...lowered));
857
+ }
858
+ catch (e) {
859
+ release();
860
+ throw e;
861
+ }
862
+ void pending.then(release, release);
863
+ return Future.deferred(pending, elementCodec(element, o));
864
+ };
865
+ }
866
+ return async (...args) => {
867
+ if (args.length !== ft.params.length) {
868
+ throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ${args.length}`);
869
+ }
870
+ const { lowered, release } = this.#lowerParams(ft.params, args, o);
871
+ let raw;
872
+ try {
873
+ raw = await fn(...lowered);
874
+ }
875
+ finally {
876
+ // Call-scoped reps minted for `borrow<R>` arguments of a
877
+ // host-implemented resource live exactly as long as the call.
878
+ release();
879
+ }
880
+ if (resultType === null)
881
+ return undefined;
882
+ if (resultType.kind === "result") {
883
+ const v = raw;
884
+ if ("error" in v) {
885
+ throw new ComponentException(resultType.error === null
886
+ ? undefined
887
+ : toHost(v["error"], resultType.error, o));
888
+ }
889
+ return resultType.ok === null
890
+ ? undefined
891
+ : toHost(v["ok"], resultType.ok, o);
892
+ }
893
+ return toHost(raw, resultType, o);
894
+ };
895
+ }
896
+ }
897
+ // ---------------------------------------------------------------------------
898
+ function rtOf(t, into, name) {
899
+ if (t === undefined)
900
+ return;
901
+ if (t.kind === "own" || t.kind === "borrow")
902
+ into.set(name, t.rt);
903
+ }
904
+ function label(leaf) {
905
+ return leaf.path.length === 0
906
+ ? leaf.interfaceId
907
+ : `${leaf.interfaceId}/${leaf.path.join("/")}`;
908
+ }
909
+ function nest(root, key, path) {
910
+ let cur = (root[key] ??= {});
911
+ for (const seg of path) {
912
+ cur = (cur[seg] ??= {});
913
+ }
914
+ return cur;
915
+ }
916
+ /** Read `names` in order from `container` after walking `path`. */
917
+ function pick(container, path, names) {
918
+ let v = container;
919
+ for (const seg of path) {
920
+ if (v === null || typeof v !== "object")
921
+ return undefined;
922
+ v = v[seg];
923
+ }
924
+ if (v === null || typeof v !== "object") {
925
+ return names.length === 0 ? v : undefined;
926
+ }
927
+ for (const n of names) {
928
+ const hit = v[n];
929
+ if (hit !== undefined)
930
+ return hit;
931
+ }
932
+ return undefined;
933
+ }
934
+ /**
935
+ * Read a DATA property from `obj` (walking its prototype chain, nearest own
936
+ * descriptor wins) without ever invoking accessors. Accessor-backed and
937
+ * absent members both yield `undefined`. Used by the A2 wrap-time suspending
938
+ * probe, which must not run platform getters against a bare prototype.
939
+ */
940
+ function dataMember(obj, key) {
941
+ for (let o = obj; o !== null && (typeof o === "object" || typeof o === "function"); o = Object.getPrototypeOf(o)) {
942
+ const d = Object.getOwnPropertyDescriptor(o, key);
943
+ if (d !== undefined)
944
+ return "value" in d ? d.value : undefined;
945
+ }
946
+ return undefined;
947
+ }
948
+ function isThenable(v) {
949
+ return v !== null && typeof v === "object" && "then" in v &&
950
+ typeof v.then === "function";
951
+ }
952
+ /**
953
+ * Drop the lifted stream/future arguments a trapping import abandoned (#66).
954
+ * Top-level parameters only: those are the shapes whose peers park host
955
+ * operations; a stream nested inside a record is exotic enough to leave to
956
+ * the negligence rules. Uses the teardown drop, not the plain one — the
957
+ * calling instance is about to be poisoned by this very trap, and a DROPPED
958
+ * notification must not queue a phantom event into its waitables (review
959
+ * B2; see task/streams.ts `dropSharedForTeardown`).
960
+ */
961
+ function releaseAsyncArgs(args) {
962
+ for (const a of args) {
963
+ if (a instanceof Stream || a instanceof Future) {
964
+ try {
965
+ a.dropForTeardown();
966
+ }
967
+ catch {
968
+ // Best-effort teardown: the component is already trapping, and that
969
+ // trap — not a secondary drop failure — is the error to surface.
970
+ }
971
+ }
972
+ }
973
+ }
974
+ function describeThrow(e) {
975
+ if (e instanceof Error)
976
+ return `${e.name}: ${e.message}`;
977
+ return describe(e);
978
+ }