@hew-lang/sandbox-vm 0.6.2 → 0.6.4

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 (40) hide show
  1. package/README.md +43 -4
  2. package/cli/test-runner.mjs +4 -1
  3. package/dist/interpreter/interpreter.d.ts +1 -1
  4. package/dist/interpreter/interpreter.js +1 -1
  5. package/dist/interpreter/parity-runner.js +26 -1
  6. package/dist/interpreter/run-program.d.ts +3 -2
  7. package/dist/interpreter/run-program.js +5 -3
  8. package/dist/interpreter/schema-validator.d.ts +9 -4
  9. package/dist/interpreter/schema-validator.js +15 -49
  10. package/dist/interpreter/types.d.ts +4 -0
  11. package/dist/interpreter/v1/browser-directory.d.ts +107 -0
  12. package/dist/interpreter/v1/browser-directory.js +288 -0
  13. package/dist/interpreter/v1/calendar.d.ts +4 -0
  14. package/dist/interpreter/v1/calendar.js +64 -0
  15. package/dist/interpreter/v1/codecs.js +2 -2
  16. package/dist/interpreter/v1/exec.d.ts +2 -2
  17. package/dist/interpreter/v1/exec.js +439 -183
  18. package/dist/interpreter/v1/instructions.generated.d.ts +1135 -0
  19. package/dist/interpreter/v1/instructions.generated.js +106 -0
  20. package/dist/interpreter/v1/package-graph.d.ts +2 -0
  21. package/dist/interpreter/v1/package-graph.js +729 -0
  22. package/dist/interpreter/v1/package-validator.generated.d.ts +3 -0
  23. package/dist/interpreter/v1/package-validator.generated.js +75846 -0
  24. package/dist/interpreter/v1/package.d.ts +5 -680
  25. package/dist/interpreter/v1/package.js +0 -12
  26. package/dist/interpreter/v1/scheduler.d.ts +5 -1
  27. package/dist/interpreter/v1/scheduler.js +32 -3
  28. package/dist/interpreter/v1/shims.d.ts +250 -7
  29. package/dist/interpreter/v1/shims.js +429 -150
  30. package/dist/interpreter/v1/structural.d.ts +2 -28
  31. package/dist/interpreter/v1/timestamp.d.ts +7 -0
  32. package/dist/interpreter/v1/timestamp.js +54 -0
  33. package/dist/interpreter/v1/validate.d.ts +8 -2
  34. package/dist/interpreter/v1/validate.js +190 -202
  35. package/dist/interpreter/v1/value-operations.d.ts +7 -2
  36. package/dist/interpreter/v1/value-operations.js +13 -6
  37. package/dist/interpreter/values.d.ts +4 -0
  38. package/dist/interpreter/values.js +6 -0
  39. package/dist/interpreter/worker.js +4 -3
  40. package/package.json +4 -4
@@ -1,31 +1,6 @@
1
1
  import { type VmValue } from "../values.js";
2
- interface Field {
3
- name: string;
4
- recipe: number;
5
- }
6
- export type StructuralRecipe = {
7
- kind: "scalar" | "unit";
8
- } | {
9
- kind: "display";
10
- callee: number;
11
- } | {
12
- kind: "identity";
13
- name: string;
14
- } | {
15
- kind: "tuple" | "vector" | "map";
16
- members: number[];
17
- } | {
18
- kind: "record";
19
- name: string;
20
- fields: Field[];
21
- } | {
22
- kind: "enum";
23
- cases: Array<{
24
- name: string;
25
- kind: "unit" | "tuple" | "struct";
26
- fields: Field[];
27
- }>;
28
- };
2
+ import type { StructuralRecipe } from "./package.js";
3
+ export type { StructuralRecipe } from "./package.js";
29
4
  /** A borrowed traversal. Display callbacks can suspend, so their callers retain
30
5
  * this stack and resume it with the returned string. No later sibling is read
31
6
  * after a callback fails. */
@@ -41,4 +16,3 @@ export declare class StructuralRenderer {
41
16
  };
42
17
  private sequence;
43
18
  }
44
- export {};
@@ -0,0 +1,7 @@
1
+ import type { VmValue } from "../values.js";
2
+ import type { PackageV1, TermV1 } from "./package.js";
3
+ type TimestampBinary = Extract<TermV1, {
4
+ op: "timestamp.binary";
5
+ }>;
6
+ export declare function timestampBinary(pkg: PackageV1, term: TimestampBinary, lhs: VmValue, rhs: VmValue): VmValue | null;
7
+ export {};
@@ -0,0 +1,54 @@
1
+ const BILLION = 1000000000n;
2
+ const MIN_I64 = -(1n << 63n);
3
+ const MAX_I64 = (1n << 63n) - 1n;
4
+ function exactNanos(pkg, value, timestamp) {
5
+ if (!timestamp) {
6
+ if (value.kind !== "i64" || value.value < MIN_I64 || value.value > MAX_I64)
7
+ throw new TypeError("timestamp offset is not a duration");
8
+ return value.value;
9
+ }
10
+ if (value.kind !== "record" ||
11
+ value.fields.length !== 2 ||
12
+ value.shape === undefined ||
13
+ pkg.aggregates[value.shape]?.builtin !== "Timestamp")
14
+ throw new TypeError("timestamp lacks its normalized pair");
15
+ const [seconds, nanos] = value.fields;
16
+ if (seconds?.kind !== "i64" ||
17
+ nanos?.kind !== "i64" ||
18
+ seconds.value < MIN_I64 ||
19
+ seconds.value > MAX_I64 ||
20
+ nanos.value < 0n ||
21
+ nanos.value >= BILLION)
22
+ throw new TypeError("invalid normalized timestamp");
23
+ return seconds.value * BILLION + nanos.value;
24
+ }
25
+ export function timestampBinary(pkg, term, lhs, rhs) {
26
+ const left = exactNanos(pkg, lhs, term.lhs_timestamp);
27
+ const right = exactNanos(pkg, rhs, term.rhs_timestamp);
28
+ const exact = term.binary_op === "Add" ? left + right : left - right;
29
+ if (term.shape === null) {
30
+ return exact < MIN_I64 || exact > MAX_I64
31
+ ? null
32
+ : { kind: "i64", value: exact };
33
+ }
34
+ if (exact < MIN_I64 * BILLION || exact > MAX_I64 * BILLION + BILLION - 1n)
35
+ return null;
36
+ let seconds = exact / BILLION;
37
+ let nanos = exact % BILLION;
38
+ if (nanos < 0n) {
39
+ seconds -= 1n;
40
+ nanos += BILLION;
41
+ }
42
+ const shape = pkg.aggregates[term.shape];
43
+ if (!shape)
44
+ throw new TypeError("timestamp result shape is absent");
45
+ return {
46
+ kind: "record",
47
+ typeId: shape.name,
48
+ shape: term.shape,
49
+ fields: [
50
+ { kind: "i64", value: seconds },
51
+ { kind: "i64", value: nanos },
52
+ ],
53
+ };
54
+ }
@@ -1,3 +1,9 @@
1
1
  import type { SandboxRejection } from "../types.js";
2
- import type { PackageV1 } from "./package.js";
3
- export declare function admitPackage(pkg: PackageV1): SandboxRejection | null;
2
+ import type { CheckedPackage } from "../schema-validator.js";
3
+ import { type HostBridges } from "./shims.js";
4
+ export declare class AdmittedPackage {
5
+ readonly bytecode: CheckedPackage;
6
+ private constructor();
7
+ static load(pkg: CheckedPackage, bridges: HostBridges): AdmittedPackage | SandboxRejection;
8
+ }
9
+ export declare function admitPackage(pkg: CheckedPackage, bridges?: HostBridges): SandboxRejection | null;
@@ -1,114 +1,17 @@
1
- /// Load-time admission for a verified SIR package.
2
- ///
3
- /// The package declares what it needs: `runtime_families` names every runtime
4
- /// call family the instruction stream reaches, and `externs` names every
5
- /// declared C-ABI symbol. This validator matches each against the shim table
6
- /// the VM implements. A family or symbol with no shim is refused before a
7
- /// single instruction runs. Unknown is rejected, never allowed.
8
- ///
9
- /// The check keys on the declared identity: it never matches a symbol prefix
10
- /// and never reads an instruction stream to guess a capability.
11
- import { SUPPORTED_NATIVE_IO, SUPPORTED_SUSPEND_KINDS, resolveExternShim, resolveRuntimeShim, } from "./shims.js";
1
+ import { calendarResultFields } from "./calendar.js";
2
+ import { CODEC_REGISTRATION, registrationRefusal, resolveExtern, resolveRuntime, resolveSuspension, suspensionRefusal, } from "./shims.js";
12
3
  const UNSUPPORTED = "sandbox.capability.unsupported";
13
- const NATIVE_FAMILIES = new Set(["AsyncIo", "FileRead", "Tcp"]);
14
- const NATIVE_SUSPENSIONS = new Set(["NativeIo", "Read", "Accept"]);
15
- const OPS = new Set([
16
- "coverage.hit",
17
- "const.int",
18
- "const.bool",
19
- "const.float",
20
- "const.char",
21
- "const.unit",
22
- "const.duration",
23
- "const.str",
24
- "const.bytes",
25
- "copy_value",
26
- "fork",
27
- "move",
28
- "destroy_value",
29
- "begin_borrow",
30
- "end_borrow",
31
- "finish_linear_receiver",
32
- "alloc_place",
33
- "store.init",
34
- "store.assign",
35
- "load.copy",
36
- "load.take",
37
- "load.borrow",
38
- "end_lifetime",
39
- "tuple.make",
40
- "tuple.get",
41
- "aggregate.make",
42
- "aggregate.project_copy",
43
- "aggregate.project_borrow",
44
- "destructure",
45
- "array.make",
46
- "array.repeat",
47
- "variant.make",
48
- "variant.is",
49
- "variant.project_copy",
50
- "variant.project_borrow",
51
- "variant.destructure",
52
- "unary",
53
- "binary",
54
- "cast",
55
- "str.eq",
56
- "bytes.eq",
57
- "function.make",
58
- "closure.make",
59
- "generator.make",
60
- "callable.coerce",
61
- "dyn.make",
62
- "register_defer",
63
- "stream.pipe",
64
- "task_scope.enter",
65
- "task_scope.close",
66
- "task.spawn",
67
- "task.invoke",
68
- "task.race",
69
- ]);
70
- const TERMS = new Set([
71
- "goto",
72
- "branch",
73
- "switch.variant",
74
- "return",
75
- "checked.binary",
76
- "dyn.call",
77
- "call",
78
- "indirect.call",
79
- "actor.call",
80
- "runtime.call",
81
- "extern.call",
82
- "value.call",
83
- "wire.codec",
84
- "panic",
85
- "checked_raise",
86
- "trap",
87
- "cleanup.dispatch",
88
- "enter_defer",
89
- "finish_defer",
90
- "recover_fault",
91
- "resume_unwind",
92
- "suspend",
93
- "unreachable",
94
- ]);
95
- const ACTOR_OPS = new Set([
96
- "identity",
97
- "spawn",
98
- "self_handle",
99
- "stop",
100
- "terminate",
101
- "await_stopped",
102
- "await_restarted",
103
- "call_start",
104
- "call_take",
105
- "submit",
106
- "stream_start",
107
- "timer_schedule",
108
- "supervisor_spawn",
109
- "supervisor_child",
110
- "supervisor_pool_view",
111
- ]);
4
+ /// The executor can only be constructed after structural/reference checks and
5
+ /// the canonical host registration have both accepted the same package.
6
+ export class AdmittedPackage {
7
+ bytecode;
8
+ constructor(bytecode) {
9
+ this.bytecode = bytecode;
10
+ }
11
+ static load(pkg, bridges) {
12
+ return admitPackage(pkg, bridges) ?? new AdmittedPackage(pkg);
13
+ }
14
+ }
112
15
  function capabilityMessage(capability, native) {
113
16
  const nativeFeatures = {
114
17
  FileRead: "Filesystem access",
@@ -140,8 +43,8 @@ function unavailable(capability) {
140
43
  span: null,
141
44
  };
142
45
  }
143
- /// The first refusal, or `null` when every declared capability has a shim.
144
- export function admitPackage(pkg) {
46
+ /// The first refusal, or `null` when all operations and bridges are available.
47
+ export function admitPackage(pkg, bridges = {}) {
145
48
  if (!pkg.entry) {
146
49
  return {
147
50
  category: "invalid_package",
@@ -151,7 +54,7 @@ export function admitPackage(pkg) {
151
54
  span: null,
152
55
  };
153
56
  }
154
- const timerResources = new Set();
57
+ const opaqueResources = new Set();
155
58
  for (const function_ of pkg.functions) {
156
59
  for (const block of function_.blocks) {
157
60
  if (block.ops.some((op) => op.op === "actor.ingress_adapter"))
@@ -170,21 +73,71 @@ export function admitPackage(pkg) {
170
73
  span: null,
171
74
  };
172
75
  if (block.term.op === "actor.call" &&
173
- !ACTOR_OPS.has(block.term.operation.op))
76
+ block.term.operation.op === "local_observation")
174
77
  return unavailable(`actor.${block.term.operation.op}`);
175
- if (block.ops.some((op) => op.op === "coverage.hit" &&
176
- (!Number.isSafeInteger(op.counter) ||
177
- op.counter < 0 ||
178
- op.counter > 0xffff_ffff))) {
179
- return {
180
- category: "invalid_package",
181
- code: "sandbox.package.invalid_counter",
182
- capability: "coverage.hit",
183
- message: "coverage counter must be a u32",
184
- span: null,
185
- };
186
- }
187
78
  const term = block.term;
79
+ if (term.op === "runtime.call" &&
80
+ pkg.runtime_families[term.family]?.family === "TimestampNow") {
81
+ const shape = term.aggregate_result_shape == null
82
+ ? undefined
83
+ : pkg.aggregates[term.aggregate_result_shape];
84
+ if (!shape ||
85
+ shape.builtin !== "Timestamp" ||
86
+ shape.fields.length !== 2) {
87
+ return {
88
+ category: "invalid_package",
89
+ code: "sandbox.package.timestamp_shape",
90
+ capability: "TimestampNow",
91
+ message: "timestamp clock lacks its checked result shape",
92
+ span: null,
93
+ };
94
+ }
95
+ }
96
+ if (term.op === "extern.call") {
97
+ const symbol = pkg.externs[term.extern]?.symbol ?? "";
98
+ const fields = calendarResultFields(symbol);
99
+ if (fields) {
100
+ const shape = term.aggregate_result_shape == null
101
+ ? undefined
102
+ : pkg.aggregates[term.aggregate_result_shape];
103
+ if (!shape ||
104
+ shape.fields.length !== fields.length ||
105
+ shape.fields.some((field, index) => field !== fields[index])) {
106
+ return {
107
+ category: "invalid_package",
108
+ code: "sandbox.package.calendar_shape",
109
+ capability: symbol,
110
+ message: "calendar operation lacks its checked result shape",
111
+ span: null,
112
+ };
113
+ }
114
+ }
115
+ }
116
+ if (term.op === "timestamp.binary") {
117
+ const both = term.lhs_timestamp && term.rhs_timestamp;
118
+ const validOperation = (term.binary_op === "Add" &&
119
+ term.lhs_timestamp !== term.rhs_timestamp) ||
120
+ (term.binary_op === "Subtract" && term.lhs_timestamp);
121
+ const shape = term.shape === null ? undefined : pkg.aggregates[term.shape];
122
+ if (typeof term.lhs_timestamp !== "boolean" ||
123
+ typeof term.rhs_timestamp !== "boolean" ||
124
+ !validOperation ||
125
+ (both
126
+ ? term.shape !== null
127
+ : !shape ||
128
+ shape.fields.length !== 2 ||
129
+ shape.builtin !== "Timestamp") ||
130
+ term.failures.length !== 1 ||
131
+ term.failures[0]?.trap !== "integer_overflow") {
132
+ return {
133
+ category: "invalid_package",
134
+ code: "sandbox.package.timestamp_shape",
135
+ capability: "timestamp.binary",
136
+ message: "timestamp arithmetic lacks its checked operands, result shape or overflow edge",
137
+ span: null,
138
+ };
139
+ }
140
+ }
188
141
  if (term.op === "runtime.call" &&
189
142
  pkg.runtime_families[term.family]?.family === "BytesDecodeUtf8") {
190
143
  const shapes = term.variant_result_shapes;
@@ -209,6 +162,26 @@ export function admitPackage(pkg) {
209
162
  span: null,
210
163
  };
211
164
  }
165
+ if (term.op === "extern.call") {
166
+ const registration = resolveExtern(pkg.externs[term.extern]);
167
+ if (registration.kind === "directory" &&
168
+ "release" in registration.operation) {
169
+ const resource = term.result_resource == null
170
+ ? undefined
171
+ : pkg.resources?.[term.result_resource];
172
+ if (resource?.kind !== "nominal" ||
173
+ resource.release !== registration.operation.release ||
174
+ term.result == null ||
175
+ term.result === "never")
176
+ return {
177
+ category: "invalid_package",
178
+ code: "sandbox.package.owner_result",
179
+ capability: null,
180
+ message: "filesystem acquisition lacks its compiler-selected owner release slot",
181
+ span: null,
182
+ };
183
+ }
184
+ }
212
185
  if (term.op === "actor.call" && term.operation.op === "timer_schedule") {
213
186
  const operation = term.operation;
214
187
  const handler = pkg.actors
@@ -229,10 +202,38 @@ export function admitPackage(pkg) {
229
202
  message: "timer schedule lacks its checked handler, timing or non-waiting admission",
230
203
  span: null,
231
204
  };
232
- timerResources.add(operation.resource);
205
+ opaqueResources.add(operation.resource);
206
+ }
207
+ if (term.op === "actor.call" &&
208
+ term.operation.op === "supervisor_spawn") {
209
+ const id = term.operation.supervisor;
210
+ const supervisor = pkg.supervisors?.find((layout) => layout.id === id);
211
+ const config = term.args.length -
212
+ (supervisor?.children.some((child) => child.is_pool) ? 1 : 0);
213
+ if (!supervisor ||
214
+ config < 0 ||
215
+ supervisor.children.some((child) => {
216
+ const params = pkg.functions.find((fn) => fn.id === child.spawn)?.params;
217
+ return !params || ![config, config + 1].includes(params.length);
218
+ }))
219
+ return {
220
+ category: "invalid_package",
221
+ code: "sandbox.package.supervisor_constructor",
222
+ capability: null,
223
+ message: "supervisor constructor signature does not match its config",
224
+ span: null,
225
+ };
226
+ }
227
+ if (term.op === "wire.codec") {
228
+ const refusal = registrationRefusal(CODEC_REGISTRATION, bridges);
229
+ if (refusal)
230
+ return rejectRegistration(refusal);
233
231
  }
234
232
  if (term.op === "suspend") {
235
- const mandatoryClose = (pkg.resources ?? []).some((resource) => resource.close === function_.id);
233
+ const registration = resolveSuspension(term);
234
+ if (registration.kind === "refused")
235
+ return rejectRegistration(registration);
236
+ const mandatoryClose = (pkg.resources ?? []).some((resource) => "close" in resource && resource.close === function_.id);
236
237
  if (term.kind === "Safepoint"
237
238
  ? (term.cancel == null) !== mandatoryClose
238
239
  : term.cancel == null)
@@ -244,33 +245,13 @@ export function admitPackage(pkg) {
244
245
  span: null,
245
246
  };
246
247
  }
247
- const unknown = block.ops.find((op) => !OPS.has(op.op))?.op ??
248
- (!TERMS.has(block.term.op) ? block.term.op : null);
249
- if (unknown)
250
- return {
251
- category: "invalid_package",
252
- code: "sandbox.package.unknown_opcode",
253
- capability: unknown,
254
- message: `unknown bytecode opcode ${unknown}`,
255
- span: null,
256
- };
257
248
  }
258
249
  }
259
250
  for (const [index, resource] of (pkg.resources ?? []).entries()) {
260
- if (resource.kind === "record" &&
261
- (resource.shape === undefined ||
262
- !pkg.aggregates.some((shape) => shape.id === resource.shape)))
263
- return {
264
- category: "invalid_package",
265
- code: "sandbox.package.resource_shape_missing",
266
- capability: null,
267
- message: "record resource has no exact aggregate shape",
268
- span: null,
269
- };
270
- if (resource.kind === "opaque" && !timerResources.has(index))
251
+ if (resource.kind === "opaque" && !opaqueResources.has(index))
271
252
  return unavailable("resource.opaque_close");
272
253
  if (resource.kind === "nominal" &&
273
- !resolveExternShim(resource.release ?? ""))
254
+ registrationRefusal(resolveExtern({ symbol: resource.release ?? "" }), bridges))
274
255
  return unavailable("resource.external_close");
275
256
  }
276
257
  for (const actor of pkg.actors ?? []) {
@@ -315,69 +296,76 @@ export function admitPackage(pkg) {
315
296
  if (actor.max_heap_bytes != null)
316
297
  return unavailable("actor.heap_limit");
317
298
  }
318
- for (const entry of pkg.runtime_families) {
319
- if (!resolveRuntimeShim(entry)) {
299
+ for (const supervisor of pkg.supervisors ?? []) {
300
+ const order = supervisor.startup_order === undefined
301
+ ? supervisor.children.map((_child, index) => index)
302
+ : supervisor.startup_order;
303
+ if (!Array.isArray(order) ||
304
+ supervisor.children.some((child) => child.dependencies !== undefined &&
305
+ (!Array.isArray(child.dependencies) ||
306
+ new Set(child.dependencies).size !== child.dependencies.length)))
320
307
  return {
321
- category: NATIVE_FAMILIES.has(entry.family)
322
- ? "native_only"
323
- : "not_implemented",
324
- code: UNSUPPORTED,
325
- capability: familyIdentity(entry.family, entry.detail),
326
- message: capabilityMessage(entry.family, NATIVE_FAMILIES.has(entry.family)),
308
+ category: "invalid_package",
309
+ code: "sandbox.package.supervisor_startup_order",
310
+ capability: null,
311
+ message: "supervisor startup order and dependencies must be arrays of unique children",
327
312
  span: null,
328
313
  };
329
- }
330
- }
331
- for (const entry of pkg.externs) {
332
- if (!resolveExternShim(entry.symbol)) {
333
- const native = entry.native_capability;
314
+ const ranks = new Map(order.map((child, rank) => [child, rank]));
315
+ if (order.length !== supervisor.children.length ||
316
+ ranks.size !== order.length ||
317
+ order.some((child) => !Number.isInteger(child) ||
318
+ child < 0 ||
319
+ child >= supervisor.children.length) ||
320
+ supervisor.children.some((child, index) => (child.dependencies ?? []).some((dependency) => !Number.isInteger(dependency) ||
321
+ dependency < 0 ||
322
+ !ranks.has(dependency) ||
323
+ ranks.get(dependency) >= ranks.get(index))))
334
324
  return {
335
- category: native === undefined ? "not_implemented" : "native_only",
336
- code: UNSUPPORTED,
337
- capability: entry.symbol,
338
- message: native === undefined
339
- ? "This external-library operation is not available in the browser runtime yet."
340
- : `${native} require native execution.`,
325
+ category: "invalid_package",
326
+ code: "sandbox.package.supervisor_startup_order",
327
+ capability: null,
328
+ message: "supervisor startup order must precede each declared dependency",
341
329
  span: null,
342
330
  };
343
- }
344
- }
345
- for (const kind of pkg.suspend_kinds) {
346
- const refused = kind === "NativeIo"
347
- ? refusedNativeIo(pkg)
348
- : SUPPORTED_SUSPEND_KINDS.has(kind)
349
- ? null
350
- : kind;
351
- if (refused !== null) {
331
+ if (supervisor.children.some((child) => !pkg.functions.some((fn) => fn.id === child.spawn)))
352
332
  return {
353
- category: NATIVE_SUSPENSIONS.has(kind)
354
- ? "native_only"
355
- : "not_implemented",
356
- code: UNSUPPORTED,
357
- capability: refused,
358
- message: capabilityMessage(kind, NATIVE_SUSPENSIONS.has(kind)),
333
+ category: "invalid_package",
334
+ code: "sandbox.package.supervisor_constructor",
335
+ capability: null,
336
+ message: "supervisor child has no constructor callable",
359
337
  span: null,
360
338
  };
361
- }
362
339
  }
363
- return null;
364
- }
365
- /// The first native I/O suspension whose operation the VM does not serve.
366
- function refusedNativeIo(pkg) {
367
- for (const function_ of pkg.functions) {
368
- for (const block of function_.blocks) {
369
- const term = block.term;
370
- if (term.op !== "suspend" || term.kind !== "NativeIo")
371
- continue;
372
- const operation = String(term.detail?.operation);
373
- if (!SUPPORTED_NATIVE_IO.has(operation))
374
- return `NativeIo::${operation}`;
375
- }
340
+ for (const entry of pkg.runtime_families) {
341
+ const refusal = registrationRefusal(resolveRuntime(entry), bridges);
342
+ if (refusal)
343
+ return rejectRegistration(refusal);
344
+ }
345
+ for (const entry of pkg.externs) {
346
+ const refusal = registrationRefusal(resolveExtern(entry), bridges);
347
+ if (refusal)
348
+ return rejectRegistration(refusal);
349
+ }
350
+ for (const kind of pkg.suspend_kinds) {
351
+ const refusal = suspensionRefusal(kind);
352
+ if (refusal)
353
+ return rejectRegistration(refusal);
376
354
  }
377
355
  return null;
378
356
  }
379
- function familyIdentity(family, detail) {
380
- return typeof detail === "string" && detail.length > 0
381
- ? `${family}::${detail}`
382
- : family;
357
+ function rejectRegistration(refusal) {
358
+ return {
359
+ category: refusal.category,
360
+ code: UNSUPPORTED,
361
+ capability: refusal.capability,
362
+ message: refusal.message ??
363
+ (refusal.capability === "codecBridge" ||
364
+ refusal.capability === "calendarBridge"
365
+ ? `This operation requires the ${refusal.capability === "codecBridge" ? "codec" : "calendar"} Wasm bridge.`
366
+ : refusal.description !== undefined
367
+ ? `${refusal.description} require native execution.`
368
+ : capabilityMessage(refusal.capability.split("::")[0], refusal.category === "native_only")),
369
+ span: null,
370
+ };
383
371
  }
@@ -1,3 +1,4 @@
1
+ import type { RuntimeContinuation } from "./shims.js";
1
2
  import { type VmValue, type RcOwner } from "../values.js";
2
3
  import { type PackageV1 } from "./package.js";
3
4
  export type ValueRequest = {
@@ -16,8 +17,12 @@ export type ValueProgram = Generator<ValueRequest, VmValue, VmValue>;
16
17
  /** Execute the exact checked plan, including every derived component selection.
17
18
  * A user method yields to an ordinary VM frame and can suspend or fail. */
18
19
  export declare function selectedValue(pkg: PackageV1, id: number, args: VmValue[]): ValueProgram;
19
- export declare function sharedOperation(operation: string, args: VmValue[], owner: RcOwner | null, typeName: string, option: (value: VmValue | null) => VmValue): ValueProgram;
20
+ export declare function sharedOperation(operation: Extract<RuntimeContinuation, {
21
+ handler: "shared";
22
+ }>["operation"], args: VmValue[], owner: RcOwner | null, typeName: string, option: (value: VmValue | null) => VmValue): ValueProgram;
20
23
  /** Open-addressed collection operations preserve native callback ordering:
21
24
  * growth hashes occupied slots before probing the incoming key. Staged slots
22
25
  * borrow their owners until every fallible callback has completed. */
23
- export declare function collectionOperation(pkg: PackageV1, family: string, operation: string, args: VmValue[], callbacks: number[], shape: number | null | undefined): ValueProgram;
26
+ export declare function collectionOperation(pkg: PackageV1, request: Extract<RuntimeContinuation, {
27
+ handler: "collection";
28
+ }>, args: VmValue[], callbacks: number[], shape: number | null | undefined): ValueProgram;
@@ -82,7 +82,7 @@ export function* selectedValue(pkg, id, args) {
82
82
  const plan = pkg.value_capabilities[id];
83
83
  if (!plan)
84
84
  throw new Error(`selected value plan ${id} is missing`);
85
- if (plan.callable !== undefined)
85
+ if (plan.callable != null)
86
86
  return yield { kind: "call", callee: plan.callable, args };
87
87
  const [left, right] = args;
88
88
  if (!left)
@@ -315,14 +315,17 @@ export function* sharedOperation(operation, args, owner, typeName, option) {
315
315
  yield { kind: "release", value: old };
316
316
  return UNIT;
317
317
  }
318
- default:
319
- throw new Error(`shared operation ${operation} has no executor`);
318
+ default: {
319
+ const exhaustive = operation;
320
+ throw new Error(`shared operation ${String(exhaustive)} has no executor`);
321
+ }
320
322
  }
321
323
  }
322
324
  /** Open-addressed collection operations preserve native callback ordering:
323
325
  * growth hashes occupied slots before probing the incoming key. Staged slots
324
326
  * borrow their owners until every fallible callback has completed. */
325
- export function* collectionOperation(pkg, family, operation, args, callbacks, shape) {
327
+ export function* collectionOperation(pkg, request, args, callbacks, shape) {
328
+ const { family, operation } = request;
326
329
  if (family === "Vector") {
327
330
  const receiver = args[0];
328
331
  if (receiver?.kind !== "vector")
@@ -382,7 +385,10 @@ export function* collectionOperation(pkg, family, operation, args, callbacks, sh
382
385
  };
383
386
  return receiver;
384
387
  }
385
- if (["Keys", "Values", "Elements", "Entries"].includes(operation)) {
388
+ if (operation === "Keys" ||
389
+ operation === "Values" ||
390
+ operation === "Elements" ||
391
+ operation === "Entries") {
386
392
  return {
387
393
  kind: "vector",
388
394
  elementType: "",
@@ -462,5 +468,6 @@ export function* collectionOperation(pkg, family, operation, args, callbacks, sh
462
468
  ? boolean(result.found)
463
469
  : option(pkg, shape, entry?.value));
464
470
  }
465
- throw new Error(`collection operation ${operation} has no executor`);
471
+ const exhaustive = operation;
472
+ throw new Error(`collection operation ${String(exhaustive)} has no executor`);
466
473
  }
@@ -51,6 +51,10 @@ export type VmValue = {
51
51
  } | {
52
52
  kind: "task";
53
53
  id: string;
54
+ } | {
55
+ kind: "host_resource";
56
+ resource: number;
57
+ owner: import("./v1/browser-directory.js").BrowserFsOwner;
54
58
  } | {
55
59
  kind: "timer";
56
60
  resource: number;