@cotal-ai/lang 0.24.0 → 0.26.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 (75) hide show
  1. package/README.md +11 -0
  2. package/dist/engine/bridge.d.ts +71 -0
  3. package/dist/engine/bridge.d.ts.map +1 -0
  4. package/dist/engine/bridge.js +277 -0
  5. package/dist/engine/bridge.js.map +1 -0
  6. package/dist/engine/ctx.d.ts +140 -0
  7. package/dist/engine/ctx.d.ts.map +1 -0
  8. package/dist/engine/ctx.js +834 -0
  9. package/dist/engine/ctx.js.map +1 -0
  10. package/dist/engine/frame.d.ts +69 -0
  11. package/dist/engine/frame.d.ts.map +1 -0
  12. package/dist/engine/frame.js +105 -0
  13. package/dist/engine/frame.js.map +1 -0
  14. package/dist/engine/host.d.ts +77 -0
  15. package/dist/engine/host.d.ts.map +1 -0
  16. package/dist/engine/host.js +134 -0
  17. package/dist/engine/host.js.map +1 -0
  18. package/dist/engine/worker-entry.d.ts +26 -0
  19. package/dist/engine/worker-entry.d.ts.map +1 -0
  20. package/dist/engine/worker-entry.js +175 -0
  21. package/dist/engine/worker-entry.js.map +1 -0
  22. package/dist/engine/worker.d.ts +156 -0
  23. package/dist/engine/worker.d.ts.map +1 -0
  24. package/dist/engine/worker.js +123 -0
  25. package/dist/engine/worker.js.map +1 -0
  26. package/dist/errors.d.ts +44 -0
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +97 -0
  29. package/dist/errors.js.map +1 -1
  30. package/dist/index.d.ts +10 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +10 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/interpret.d.ts +7 -38
  35. package/dist/interpret.d.ts.map +1 -1
  36. package/dist/interpret.js +45 -1003
  37. package/dist/interpret.js.map +1 -1
  38. package/dist/journal.d.ts.map +1 -1
  39. package/dist/journal.js +75 -1
  40. package/dist/journal.js.map +1 -1
  41. package/dist/library.d.ts.map +1 -1
  42. package/dist/library.js +13 -1
  43. package/dist/library.js.map +1 -1
  44. package/dist/perform.d.ts +138 -0
  45. package/dist/perform.d.ts.map +1 -0
  46. package/dist/perform.js +1052 -0
  47. package/dist/perform.js.map +1 -0
  48. package/dist/pins.d.ts +28 -8
  49. package/dist/pins.d.ts.map +1 -1
  50. package/dist/pins.js +31 -11
  51. package/dist/pins.js.map +1 -1
  52. package/dist/sim.d.ts +15 -1
  53. package/dist/sim.d.ts.map +1 -1
  54. package/dist/sim.js.map +1 -1
  55. package/dist/transform/emit.d.ts +23 -0
  56. package/dist/transform/emit.d.ts.map +1 -0
  57. package/dist/transform/emit.js +934 -0
  58. package/dist/transform/emit.js.map +1 -0
  59. package/dist/transform/index.d.ts +34 -0
  60. package/dist/transform/index.d.ts.map +1 -0
  61. package/dist/transform/index.js +31 -0
  62. package/dist/transform/index.js.map +1 -0
  63. package/dist/transform/scope.d.ts +58 -0
  64. package/dist/transform/scope.d.ts.map +1 -0
  65. package/dist/transform/scope.js +500 -0
  66. package/dist/transform/scope.js.map +1 -0
  67. package/dist/transform/seam.d.ts +78 -0
  68. package/dist/transform/seam.d.ts.map +1 -0
  69. package/dist/transform/seam.js +111 -0
  70. package/dist/transform/seam.js.map +1 -0
  71. package/dist/values.d.ts +15 -0
  72. package/dist/values.d.ts.map +1 -1
  73. package/dist/values.js +79 -0
  74. package/dist/values.js.map +1 -1
  75. package/package.json +9 -4
@@ -0,0 +1,834 @@
1
+ /**
2
+ * `__ctx`: the WHOLE seam between the transformed program and the host.
3
+ *
4
+ * The transform emits a CLOSED function expression with zero free identifiers; the host
5
+ * evaluates it in a Compartment with ZERO endowments and passes this object as the call argument
6
+ * (the seam contract). So this file is the complete list of what a program can reach. Anything not
7
+ * here is not reachable, which is why the member list is a surface both sides hold each other to.
8
+ *
9
+ * Every law lives here, and every table it needs is IMPORTED. `library.ts` owns the curated method
10
+ * tables and the free builtins; `values.ts` owns freezing, birth-depth stamping and the crossing
11
+ * check; `keys.ts` owns key allocation; `journal.ts` owns the entries. Nothing in this file is a
12
+ * second copy of any of them: a copied table is a table that disagrees with the walker on its
13
+ * first edit, and the walker is the differential oracle.
14
+ *
15
+ * WHAT THIS FILE IS NOT: it is not the walker rewritten. The walker enforces these laws inline
16
+ * while it walks; here they are the host side of a native program's calls. The behaviours must be
17
+ * identical and the differential suite is what says so: same programs, walker and engine,
18
+ * identical journals (entry sequences and step keys, not merely output).
19
+ */
20
+ import { RuntimeFault } from "../errors.js";
21
+ import { Cancelled, EffectError } from "../effects.js";
22
+ import { arrayMethods, builtins, numberMethods, stringMethods } from "../library.js";
23
+ import { NotCrossable, Prng, assertNoCode, birthDepth, born as stampBirth, deepFreeze, setOwn } from "../values.js";
24
+ import { digest } from "../keys.js";
25
+ import { currentFrame, withFrame } from "./frame.js";
26
+ import { dispatchPrimitive, freeConstructors, option, performScope, runScope } from "../perform.js";
27
+ import { PRIMITIVES } from "../primitives.js";
28
+ // ---- the coercion law, shared by every site that can reach host ToPrimitive --------------------
29
+ /**
30
+ * L4018, the one refusal that has to be spelled identically everywhere it applies.
31
+ *
32
+ * Converting reads `valueOf`/`toString` off the value, and a program closure stored there would run
33
+ * from host machinery with no frame. The walker measured every one of these holes before closing
34
+ * them; the engine inherits the closed shape rather than rediscovering it.
35
+ */
36
+ function refuseCoercion(where, v) {
37
+ if (v !== null && (typeof v === "object" || typeof v === "function")) {
38
+ const kind = typeof v === "function" ? "a function" : Array.isArray(v) ? "an array" : "a record";
39
+ throw new RuntimeFault("L4018", `\`${where}\` cannot take ${kind}: there is no implicit conversion here, because converting would read \`valueOf\`/\`toString\` off the value — host machinery this language does not have. Convert explicitly: \`json.stringify(value)\` for text, or read the field you mean.`);
40
+ }
41
+ }
42
+ /** A canonical array index (`"0"`, `"12"`), as a number, or nothing. */
43
+ function arrayIndex(prop) {
44
+ if (!/^(0|[1-9][0-9]*)$/.test(prop))
45
+ return undefined;
46
+ const n = Number(prop);
47
+ return n <= 4294967294 ? n : undefined;
48
+ }
49
+ // ---- the thenable gate -------------------------------------------------------------------------
50
+ /**
51
+ * An own CALLABLE `then` on a program value, refused where it is minted.
52
+ *
53
+ * Measured at 9dc154f8 (node v26.7.0, ses@2.3.0), inside the compartment, one shot per site:
54
+ * constructing such a record is harmless, but RETURNING it out of any async function assimilates
55
+ * it (`then(resolve, reject)` runs, with the host's settlement functions as its arguments), and so
56
+ * does awaiting it, and so does a function carrying an own `then`. Every transformed function body
57
+ * is async, so the hazard sits at every return of a program value, not merely at `await`.
58
+ *
59
+ * The refusal is scoped to a CALLABLE `then` because a non-callable one is legal and settles clean
60
+ * on the walker (`{ then: 1 }` runs to completion); refusing every own `then` would refuse a program
61
+ * the oracle accepts.
62
+ *
63
+ * THE CODE IS L4021, the walker's, since #657 gave the walker the same rule at its four record-member
64
+ * write sites (a literal, a spread, a rest pattern, `o.a = v`). Measured across every route a program
65
+ * can spell it, both engines now answer L4021 where the walker does and the same older code where it
66
+ * already agreed: a literal, a plain field write, a computed one and a spread are L4021 on both; a
67
+ * non-callable `then` completes on both; `then` on an ARRAY is L4014 on both, and on a string or a
68
+ * number L4010 on both. The one door with NO walker route behind it is `await`, which gates a value
69
+ * arriving from the HOST side of the seam - the walker has no such door because it has no seam. It
70
+ * carries L4021 too, so a program reads one code for one shape rather than learning which door
71
+ * it happened to come through.
72
+ *
73
+ * Two doors close the whole value graph, by induction: a callable `then` can only enter through a
74
+ * literal (`born`) or a field write (`set`): `json.parse` cannot spell a function, and spread,
75
+ * `merge` and the array methods only copy fields out of records that already passed a door. `await`
76
+ * is gated too, because a value can also arrive from the host side of the seam.
77
+ *
78
+ * THERE IS DELIBERATELY NO CHECK ON THE HOST'S RETURN PATH, and the reason is measured rather than
79
+ * argued. Such a check was asked for at `free`/`call`, on the strength of `merge({}, { then: f })`
80
+ * minting the shape on the WALKER. It cannot exist: every builtin and curated method in library.ts
81
+ * returns through its own `async` wrapper, so the assimilation happens INSIDE library.ts, one frame
82
+ * before any host code could inspect the result. Measured directly against `merge`: with a `then`
83
+ * that never resolves, the builtin's promise never settles at all (the program closure had already
84
+ * run); with a `then` that resolves, the record is silently REPLACED by whatever it resolves with.
85
+ * In both cases the value a return-path gate would examine is either never delivered or already
86
+ * substituted, so such a gate is unreachable code that no mutant can kill. The walker shape it was
87
+ * meant to cover is a walker defect (there is no birth gate there at all) and belongs to the filed
88
+ * issue; in the engine the literal never reaches `merge`, because `born` refuses it first, which is
89
+ * a cell, not an assertion.
90
+ */
91
+ function hasCallableThen(v) {
92
+ if (v === null || (typeof v !== "object" && typeof v !== "function"))
93
+ return false;
94
+ if (!Object.prototype.hasOwnProperty.call(v, "then"))
95
+ return false;
96
+ return typeof v.then === "function";
97
+ }
98
+ function refuseThenable(v, where) {
99
+ if (!hasCallableThen(v))
100
+ return;
101
+ throw new RuntimeFault("L4021", `${where} carries an own callable \`then\`. To the host's promise machinery any object with a callable \`then\` is a promise waiting to be adopted, so the value would never arrive as the one this program built: its \`then\` runs with the machinery's own continuations, a \`then\` that throws or rejects escapes the run as an unhandled rejection with no owner and kills the host, and the await that adopted it never settles. Name the member something else.`);
102
+ }
103
+ // ---- the seam ----------------------------------------------------------------------------------
104
+ /**
105
+ * The seam, plus the one thing the HOST needs from it that the program must never see.
106
+ *
107
+ * `steps()` is the count charged against the step budget, which a `RunResult` reports so a host can
108
+ * see how close a program runs to the ceiling before the ceiling is what tells it. It is deliberately
109
+ * NOT a member of {@link EngineCtx}: everything on that interface is reachable from inside the
110
+ * compartment, and a program that can read its own fuel gauge can shape its behaviour around one.
111
+ */
112
+ export function createEngine(run) {
113
+ const ctx = buildCtx(run);
114
+ return { ctx, steps: () => ctx[STEPS]() };
115
+ }
116
+ /** The seam alone. */
117
+ export function createCtx(run) {
118
+ return buildCtx(run);
119
+ }
120
+ /** Where the step count hides: a symbol the language cannot name, on the object the program holds. */
121
+ const STEPS = Symbol("cotal-lang engine steps");
122
+ function buildCtx(run) {
123
+ const prng = new Prng(run.pins.seed);
124
+ // The effect seam is ONE function over ONE table, shared with the walker (src/perform.ts). The
125
+ // engine holds no copy of it: a second set of hashed projections is a set that disagrees with the
126
+ // oracle on its first edit, and the journal is the contract the differential suite compares.
127
+ //
128
+ // THE CEILING IS A RUN BOUND, so the count starts where the run left off. Starting at 0 gives
129
+ // every activation a full allowance, and a runaway loop that crashed periodically never reaches
130
+ // the ceiling however much it performed against the world.
131
+ const host = {
132
+ journal: run.journal,
133
+ options: {
134
+ runId: run.runId,
135
+ handler: run.handler,
136
+ journal: run.journal,
137
+ pins: run.pins,
138
+ ...(run.onLog !== undefined ? { onLog: run.onLog } : {}),
139
+ ...(run.shouldStop !== undefined ? { shouldStop: run.shouldStop } : {}),
140
+ },
141
+ ceiling: run.pins.effectCeiling,
142
+ effectCount: run.journal.dispatchedEffects(),
143
+ };
144
+ /** May this frame write into this container? The value half of freeze-on-share, whole. */
145
+ const assertWritable = (target, frame) => {
146
+ if (Object.isFrozen(target)) {
147
+ throw new RuntimeFault("L2031", "this value crossed an effect boundary and is frozen: what crossed is what the journal recorded, so it cannot change afterwards. Build a new value instead: `{ ...record, field: value }` or `[...list, item]`.");
148
+ }
149
+ if (birthDepth(target) < frame.depth) {
150
+ throw new RuntimeFault("L2032", "this value was built outside this concurrent branch and is written inside it. Two branches writing one value is nondeterministic, and it is silent: live they write in completion order, on resume the recorded effects return instantly and they write in launch order, so the value differs and the run takes a path it never recorded. Build the value inside the branch and return it, and read it out of the combinator's result.");
151
+ }
152
+ };
153
+ // A LOG LINE IS DATA on this engine: a function anywhere inside a logged value is refused HERE, in
154
+ // the one sink every log line leaves through, before it reaches any transport - the in-process host
155
+ // callback or the worker thread, which cannot even clone one (measured, unguarded: the thread died
156
+ // on the host's DataCloneError with the emitted module body in its message). Held whether or not the
157
+ // host is listening, so a program's outcome never depends on who is watching; refused as the builtin
158
+ // refuses (L4016 naming the value and the path), catchable, the way json.stringify refuses. The
159
+ // WALKER is untouched on purpose: it replays every run recorded under language version 1, and a v1
160
+ // record that logs a builtin must replay as recorded. This is a rule of the engine, declared as a
161
+ // divergence in the differential suite.
162
+ const onLog = (line) => {
163
+ line.values.forEach((v, i) => {
164
+ try {
165
+ assertNoCode(v, `log: value ${i + 1}`);
166
+ }
167
+ catch (e) {
168
+ if (e instanceof NotCrossable)
169
+ throw new RuntimeFault("L4016", e.message);
170
+ throw e;
171
+ }
172
+ });
173
+ run.onLog?.(line);
174
+ };
175
+ const libraryContext = {
176
+ runId: run.runId,
177
+ programHash: run.programHash,
178
+ startedAt: run.pins.startedAt,
179
+ prng,
180
+ onLog,
181
+ assertWritable,
182
+ };
183
+ const methods = {
184
+ array: arrayMethods(libraryContext),
185
+ string: stringMethods(),
186
+ number: numberMethods(),
187
+ };
188
+ const freeNames = new Map(builtins(libraryContext).map(([n, v]) => [n, v]));
189
+ // The two pure primitives and the four event constructors are free VALUES, not journalled effects
190
+ // (design §3A). They come from the SAME table the walker declares them from (perform.ts), wrapped
191
+ // into the internal `(frame, args)` convention here so one `free` serves every name: a second copy
192
+ // of the shapes is a second answer to what a handle or an event descriptor IS on the wire.
193
+ for (const [name, impl] of freeConstructors({ runId: run.runId, programHash: run.programHash, startedAt: run.pins.startedAt })) {
194
+ freeNames.set(name, ((_frame, a) => impl(a)));
195
+ }
196
+ // ---- the calling-convention adapter, both directions ----------------------------------------
197
+ //
198
+ // The walker's convention is `(frame, args)`, and library.ts calls every callback that way. The
199
+ // transform emits plain `async (...args)` closures and never a frame parameter. So the
200
+ // HOST adapts at every crossing, in both directions, and this section is the whole of it.
201
+ //
202
+ // THE INVARIANT: adaptation may never be observable from inside the program. A value
203
+ // the program hands to a library function and reads back must behave AND compare `===` as the one
204
+ // it handed in. Two things follow, and both were live defects before they were rules:
205
+ //
206
+ // * ONLY A POSITION THE LIBRARY CALLS IS ADAPTED. Adapting every function in an argument list
207
+ // rewrote the ones a mutating method STORES: `xs.push(f)` put the walker view in the array,
208
+ // and the program read back a value that neither called (`args is not iterable`) nor compared
209
+ // equal to the one it pushed.
210
+ // * AN ADAPTER IS MINTED ONCE PER VALUE. A fresh closure per read makes `map !== map`, which is
211
+ // false on the walker, where a builtin is one immutable binding for the whole run.
212
+ /**
213
+ * Where a library function calls one of its arguments, by qualified name.
214
+ *
215
+ * Derived from library.ts's `asCallable` sites: the eleven callback-taking array methods take
216
+ * theirs FIRST, the six higher-order builtins take theirs SECOND (the list is first). It is held
217
+ * to those sites BEHAVIOURALLY rather than by reading them: a cell probes every name in every
218
+ * table with a marker function and compares the set of positions the library actually calls to
219
+ * this one, so a table that grows a callback position reds here instead of drifting.
220
+ */
221
+ const CALLBACK_ARG = new Map([
222
+ ...["map", "filter", "find", "findIndex", "findLast", "findLastIndex", "some", "every", "forEach", "reduce", "flatMap"].map((n) => [`array.${n}`, 0]),
223
+ ...["map", "filter", "find", "some", "every", "sort"].map((n) => [`builtin.${n}`, 1]),
224
+ ]);
225
+ /** Host value -> the program's view of it, so a value has ONE view for the life of the run. */
226
+ const programView = new WeakMap();
227
+ /** A program view -> the host callable it wraps, so a round trip through the seam is the identity. */
228
+ const hostOf = new WeakMap();
229
+ /** Program closure -> the walker's view of it, for the same reason in the other direction. */
230
+ const walkerView = new WeakMap();
231
+ /** A program closure, as library.ts calls it. The frame travels explicitly, not by ambience. */
232
+ const toWalker = (fn) => {
233
+ const underlying = hostOf.get(fn);
234
+ if (underlying !== undefined)
235
+ return underlying;
236
+ const had = walkerView.get(fn);
237
+ if (had !== undefined)
238
+ return had;
239
+ const w = async (frame, args) => await withFrame(frame, () => fn(...args));
240
+ walkerView.set(fn, w);
241
+ return w;
242
+ };
243
+ /** Adapt the ONE argument this library function calls, and nothing else in the list. */
244
+ const adaptArgs = (key, args) => {
245
+ const at = CALLBACK_ARG.get(key);
246
+ if (at === undefined || typeof args[at] !== "function")
247
+ return args;
248
+ const out = args.slice();
249
+ out[at] = toWalker(args[at]);
250
+ return out;
251
+ };
252
+ /**
253
+ * A host value on its way OUT to the program: any `(frame, args)` callable in it becomes a plain
254
+ * closure that adapts its own arguments the same way the call form does. Only `json` needs the
255
+ * record walk, and it is walked rather than special-cased so a builtin that grows members later
256
+ * does not silently hand out the wrong convention.
257
+ */
258
+ const toProgramValue = (name, v) => {
259
+ if (v === null || (typeof v !== "object" && typeof v !== "function"))
260
+ return v;
261
+ const had = programView.get(v);
262
+ if (had !== undefined)
263
+ return had;
264
+ let out;
265
+ if (typeof v === "function") {
266
+ const key = `builtin.${name}`;
267
+ const p = async (...args) => await v(currentFrame(), adaptArgs(key, args));
268
+ hostOf.set(p, v);
269
+ out = p;
270
+ }
271
+ else if (Array.isArray(v)) {
272
+ out = v;
273
+ }
274
+ else {
275
+ const rec = {};
276
+ for (const [k, inner] of Object.entries(v))
277
+ setOwn(rec, k, toProgramValue(`${name}.${k}`, inner));
278
+ out = deepFreeze(rec);
279
+ }
280
+ programView.set(v, out);
281
+ return out;
282
+ };
283
+ // ---- the concurrency scopes ------------------------------------------------------------------
284
+ //
285
+ // The SAME two functions the walker calls, with the same arguments in the same order:
286
+ // `performScope` owns the journal entry and the replay, `runScope` owns what a scope means. None
287
+ // of a race's winner rule, a fanOut's key rule or a conclave's close lives on this side, because
288
+ // a second copy of that logic would be a second answer to what a scope IS.
289
+ //
290
+ // What this side owns is the two things the engine has that the walker does not: the CALLING
291
+ // CONVENTION (arms arrive as the program's own closures, and the scope machinery calls them
292
+ // `(frame, args)`) and the MISSING AST (a settled race's `branchDigest` is a function of the
293
+ // source, so it arrives as the call site's static payload instead).
294
+ /** An arm, as the scope machinery calls one. A non-function is passed through so the engine fails where the walker fails. */
295
+ const asArm = (v) => (typeof v === "function" ? toWalker(v) : v);
296
+ /** `parallel`/`race` take a record or an array OF ARMS; the other two take data in that position. */
297
+ const branchesOf = (name, first) => {
298
+ if (name !== "parallel" && name !== "race")
299
+ return first;
300
+ if (Array.isArray(first))
301
+ return first.map(asArm);
302
+ if (first === null || typeof first !== "object")
303
+ return first;
304
+ const out = {};
305
+ for (const [k, v] of Object.entries(first))
306
+ setOwn(out, k, asArm(v));
307
+ return out;
308
+ };
309
+ /** `fanOut`'s `key` is called by the scope machinery too. The copy never reaches the program. */
310
+ const bagWithKey = (bag) => {
311
+ const key = option(bag, "key");
312
+ if (typeof key !== "function")
313
+ return bag;
314
+ const out = {};
315
+ for (const [k, v] of Object.entries(bag))
316
+ setOwn(out, k, v);
317
+ setOwn(out, "key", asArm(key));
318
+ return out;
319
+ };
320
+ /**
321
+ * The second argument, DEFERRED - and it must be, measured on the walker rather than argued:
322
+ *
323
+ * fanOut(xs, await choose(), { name: "f", key }) journal ["fanOut:f", "sleep:warm"]
324
+ * conclave([a], await choose(), { name: "c" }) journal ["spawn:hire", "conclave:c", "sleep:warm"]
325
+ * fanOut(xs, fn, { name: "f", key: await choose() }) journal ["sleep:warm", "fanOut:f"]
326
+ *
327
+ * The body is evaluated INSIDE the scope, after its entry has begun; the options bag is evaluated
328
+ * before it. So the emitted call hands the body over as a thunk, for the same reason the optional
329
+ * call hands over its arguments as one: an argument that was already evaluated has already
330
+ * journalled its effects in the wrong place, and a resume would replay a step the walker's run
331
+ * never recorded. `parallel` and `race` have no deferred argument - their second is the bag.
332
+ */
333
+ const deferredBody = (name, args) => async () => {
334
+ if (name === "parallel" || name === "race") {
335
+ throw new RuntimeFault("L1000", `\`${name}\` has no deferred argument; asking for one is an engine fault`);
336
+ }
337
+ const thunk = args[1];
338
+ if (typeof thunk !== "function") {
339
+ throw new RuntimeFault("L1000", `\`${name}\` takes its body UNEVALUATED, as a thunk: the walker evaluates it AFTER the scope's entry has begun (measured: an effect in that position journals inside the scope, one in the options bag journals before it), so a body handed over already evaluated has journalled its effects in the wrong place.`);
340
+ }
341
+ return asArm(await thunk());
342
+ };
343
+ /**
344
+ * The `branchDigest`, rebuilt from the call site's payload with the walker's own `digest`.
345
+ *
346
+ * The walker digests `[...losers].sort().map((n) => [n, bodies.get(n) ?? null])` over the arm
347
+ * bodies with positions stripped, and a name the site does not carry digests as `null`. An arm
348
+ * that was RENAMED is exactly the case this has to notice. Absent `branchDigests` means the arms
349
+ * were not written as an object literal at the call, which is where the walker also answers
350
+ * undefined, so the field's presence is the whole decision.
351
+ */
352
+ const digesterFor = (site) => {
353
+ const bodies = site?.branchDigests;
354
+ if (bodies === undefined)
355
+ return undefined;
356
+ return (losers) => digest([...losers]
357
+ .sort()
358
+ .map((n) => [n, Object.prototype.hasOwnProperty.call(bodies, n) ? bodies[n] : null]));
359
+ };
360
+ const openScope = async (name, spec, args, site) => {
361
+ const frame = currentFrame();
362
+ const scopeKind = name;
363
+ const first = args[0];
364
+ const bag = args[spec.optionsAt];
365
+ const scopeName = option(bag, "name") ?? null;
366
+ // Allocated HERE, synchronously, exactly as the walker allocates it: the occurrence is what
367
+ // makes two textually identical scopes different steps, and a counter read after an await is a
368
+ // counter two scopes can race for.
369
+ const occurrence = frame.keys.nextScope(scopeKind, scopeName);
370
+ const scopeKey = frame.keys.scopeKey(scopeKind, scopeName, occurrence);
371
+ // `conclave` is the one scope whose identity includes a SUBJECT: the members are what the
372
+ // sub-team IS, so editing the member list diverges rather than resuming into a different room.
373
+ const subject = spec.hashesSubject
374
+ ? {
375
+ members: first.map((m) => m.agent),
376
+ channel: option(bag, "channel") ?? null,
377
+ }
378
+ : undefined;
379
+ return await performScope(host, scopeKey, frame, async (ctx, only) => await runScope(host, name, scopeKind, scopeName, occurrence, branchesOf(name, first), deferredBody(name, args), bagWithKey(bag), frame, ctx, only), subject,
380
+ // `race` alone, as on the walker: `parallel` and `fanOut` have no losers, and a `conclave`
381
+ // cannot be walked into at all.
382
+ name === "race" ? digesterFor(site) : undefined);
383
+ };
384
+ // ---- fuel ------------------------------------------------------------------------------------
385
+ //
386
+ // The unit CHANGES from the walker's: the walker charges one dispatch per node it walks, the
387
+ // engine charges one transformed-site hit. That is languageVersion 2's pin-unit change, and it is
388
+ // why L4013 is a FIRST-PARTY cell rather than a differential one: journals are unaffected,
389
+ // because steps are never recorded, but the budget fires at different points on the two engines.
390
+ let steps = 0;
391
+ let nextYield = run.pins.yieldEvery;
392
+ const breathe = async (frame) => {
393
+ // The MACROTASK queue, not a microtask. A program that only ever yields microtasks starves the
394
+ // host completely: a watchdog's setTimeout never fires, and the run takes down the timer plane
395
+ // and every other run on the host with it.
396
+ await new Promise((resolve) => {
397
+ setTimeout(resolve, 0);
398
+ });
399
+ // The cut, and only the cut: an arm that can no longer win is abandoned here. An arm that could
400
+ // still win keeps running its pure work, so a live race is decided by the recorded clocks and
401
+ // declaration order rather than by how many steps a tail happens to take against `yieldEvery`.
402
+ if (frame.signal.cutPure)
403
+ throw new Cancelled(frame.signal.reason ?? "cancelled");
404
+ };
405
+ const fuel = () => {
406
+ steps += 1;
407
+ if (steps > run.pins.stepBudget) {
408
+ throw new RuntimeFault("L4013", `this walk has taken more than ${run.pins.stepBudget} interpreter steps without finishing, which means a loop that performs no effect is not terminating. The effect ceiling cannot see such a loop, because it performs nothing to count. Add an exit condition, or raise stepBudget if the program legitimately does this much work. (stepBudget bounds ONE WALK, not the run: steps are not recorded, so a resume cannot recover a count the way the effect ceiling can.)`);
409
+ }
410
+ // Returns nothing on the common path, so the emitted `await __ctx.fuel()` costs a bare microtask
411
+ // rather than an allocated promise.
412
+ if (steps < nextYield)
413
+ return;
414
+ nextYield = steps + run.pins.yieldEvery;
415
+ return breathe(currentFrame());
416
+ };
417
+ // ---- members ---------------------------------------------------------------------------------
418
+ /** The property key a member expression names, as JavaScript would spell it. */
419
+ const keyOf = (k) => {
420
+ if (typeof k === "string")
421
+ return k;
422
+ refuseCoercion("[...]", k);
423
+ return String(k);
424
+ };
425
+ const methodOf = (table, receiver, prop, kind, asCallee) => {
426
+ const m = table[prop];
427
+ if (m === undefined) {
428
+ throw new RuntimeFault("L4014", `\`${prop}\` is not a member of ${kind}. The members are: length, an index, ${Object.keys(table).join(", ")}.`);
429
+ }
430
+ // A method is looked up AT THE CALL and exists nowhere else, a declared difference from
431
+ // JavaScript, where `xs.map` is a value. Handing one out gives `xs.map !== xs.map` and an
432
+ // extracted `push` that writes to a receiver strict JavaScript would refuse.
433
+ if (!asCallee) {
434
+ throw new RuntimeFault("L4020", `\`${prop}\` is a method of ${kind}, and a method is not a value here: it is looked up at the call, so it cannot be extracted, compared, or passed. Call it — \`.${prop}(...)\` — or wrap it: \`(...args) => value.${prop}(...args)\`.`);
435
+ }
436
+ return async (frame, args) => await m(frame, receiver, args);
437
+ };
438
+ const memberOf = (obj, prop, asCallee) => {
439
+ switch (typeof obj) {
440
+ case "string": {
441
+ if (prop === "length")
442
+ return obj.length;
443
+ const i = arrayIndex(prop);
444
+ if (i !== undefined)
445
+ return obj[i];
446
+ return methodOf(methods.string, obj, prop, "a string", asCallee);
447
+ }
448
+ case "number":
449
+ return methodOf(methods.number, obj, prop, "a number", asCallee);
450
+ case "object": {
451
+ if (obj === null)
452
+ throw new RuntimeFault("L4010", `cannot read \`${prop}\` of null`);
453
+ if (Array.isArray(obj)) {
454
+ if (prop === "length")
455
+ return obj.length;
456
+ const i = arrayIndex(prop);
457
+ if (i !== undefined)
458
+ return obj[i];
459
+ return methodOf(methods.array, obj, prop, "an array", asCallee);
460
+ }
461
+ // A record answers its OWN fields and `undefined` for anything else, so no host prototype is
462
+ // ever reached: `o.constructor`, `o.toString` and `o.hasOwnProperty` are all `undefined`.
463
+ return Object.prototype.hasOwnProperty.call(obj, prop) ? obj[prop] : undefined;
464
+ }
465
+ case "undefined":
466
+ throw new RuntimeFault("L4010", `cannot read \`${prop}\` of undefined`);
467
+ default:
468
+ throw new RuntimeFault("L4014", `\`${prop}\` is not a member: a ${typeof obj} has no members`);
469
+ }
470
+ };
471
+ /** Which curated table a receiver answers from, and the first half of a member's qualified name. */
472
+ const tableKind = (obj) => typeof obj === "string" ? "string" : typeof obj === "number" ? "number" : Array.isArray(obj) ? "array" : undefined;
473
+ /**
474
+ * The same lookup as {@link memberOf}, keeping WHERE the member came from.
475
+ *
476
+ * The two paths are told apart by the NAME, never by what the name answered. Deciding on
477
+ * `typeof v === "function"` reads right and is wrong: an array element can itself be a program
478
+ * closure, so `fs[0]("a")` classified as a curated method and was called `(frame, ["a"])`, with
479
+ * the frame handed to the program as its first argument, which is the hazard this whole section
480
+ * exists to close. Measured on `const fs = [(x) => x]` before the fix.
481
+ */
482
+ const lookup = (obj, prop, asCallee) => {
483
+ const kind = tableKind(obj);
484
+ if (kind === undefined)
485
+ return { from: "own", value: memberOf(obj, prop, asCallee) };
486
+ if (typeof obj === "string" || Array.isArray(obj)) {
487
+ if (prop === "length" || arrayIndex(prop) !== undefined)
488
+ return { from: "own", value: memberOf(obj, prop, asCallee) };
489
+ }
490
+ return { from: "table", fn: memberOf(obj, prop, asCallee), key: `${kind}.${prop}` };
491
+ };
492
+ /**
493
+ * THE CELL TEST, in ONE place because both doors ask the same question.
494
+ *
495
+ * A binding the transform turned into a cell exists for its whole block, and the cell record is
496
+ * hoisted to the top of that block so the closures capturing it have something to close over. What
497
+ * decides whether the DECLARATION has run is whether the key is THERE - `hasOwn`, never truthiness,
498
+ * because a binding initialised to `undefined` has run and a truthiness test would refuse it.
499
+ *
500
+ * Written once rather than at each door: a duplicated guard is one whose mutant can be defeated by
501
+ * copying it, and the read and the write differ in their SENTENCE, not in this question.
502
+ */
503
+ const declarationHasRun = (cell, prop) => cell !== null && typeof cell === "object" && Object.prototype.hasOwnProperty.call(cell, prop);
504
+ const ctx = {
505
+ fuel,
506
+ get(o, k, binding) {
507
+ const prop = keyOf(k);
508
+ // THE CELL DOOR. A binding the transform turned into a cell exists for its whole block, and
509
+ // the cell record is hoisted to the top of that block so the closures capturing it have
510
+ // something to close over. What decides whether the DECLARATION has run is whether the key is
511
+ // there - `hasOwn`, never truthiness, because a binding initialised to `undefined` has run.
512
+ // The walker's own words, so a program cannot tell which engine refused it.
513
+ if (binding !== undefined && !declarationHasRun(o, prop)) {
514
+ throw new RuntimeFault("L2004", `${binding} is used before its declaration was reached: the binding exists for the whole block, but it holds no value until the \`let\`/\`const\` line runs. Call this function after the declaration, or move the declaration up.`);
515
+ }
516
+ return memberOf(o, prop, false);
517
+ },
518
+ set(o, k, v, binding) {
519
+ const prop = keyOf(k);
520
+ // THE OTHER CELL DOOR, and it is a SEPARATE refusal rather than the read's reused: the walker
521
+ // answers a different sentence for a write, and a program that could tell the two engines apart
522
+ // by which sentence it caught would be a divergence dressed as a message. Both are catchable,
523
+ // both leave the binding uninitialised, and the declaration still initialises it when reached.
524
+ // Emitted only on an assignment - the declaration's own `set` passes three arguments - so a
525
+ // cell being written for the first time by its `let` line never comes through here.
526
+ if (binding !== undefined && !declarationHasRun(o, prop)) {
527
+ throw new RuntimeFault("L2004", `${binding} is assigned before its declaration was reached: the binding exists for the whole block, but it holds no value until the \`let\`/\`const\` line runs.`);
528
+ }
529
+ if (o === null || o === undefined || typeof o !== "object") {
530
+ throw new RuntimeFault("L4010", `cannot write \`${prop}\` of ${o === null ? "null" : typeof o === "undefined" ? "undefined" : `a ${typeof o}`}`);
531
+ }
532
+ assertWritable(o, currentFrame());
533
+ if (Array.isArray(o)) {
534
+ if (prop === "length") {
535
+ // `xs.length = n` truncates, as in JavaScript. A LONGER length is refused: JavaScript
536
+ // would fill the gap with holes, and a hole is a value class this language does not have.
537
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 0 || v > o.length) {
538
+ throw new RuntimeFault("L4017", `\`length\` can only be set to an integer between 0 and the array's current length (${o.length}), got ${typeof v === "number" ? v : typeof v}: a longer length would create holes, which this language does not have; push the elements instead`);
539
+ }
540
+ o.length = v;
541
+ return v;
542
+ }
543
+ const i = arrayIndex(prop);
544
+ if (i === undefined) {
545
+ throw new RuntimeFault("L4014", `\`${prop}\` is not a member of an array: an array takes an index or \`length\``);
546
+ }
547
+ if (i > o.length) {
548
+ throw new RuntimeFault("L4019", `index ${i} is past the end of this array (length ${o.length}), and JavaScript would fill the gap with holes, which this language does not have. Write at an existing index, at the length to append, or use \`push\`.`);
549
+ }
550
+ }
551
+ else if (prop === "__proto__") {
552
+ throw new RuntimeFault("L4014", "`__proto__` names an object's prototype, and there are no prototypes here");
553
+ }
554
+ // THE SECOND DOOR OF THE THENABLE GATE, and it is LAST on purpose. A record can acquire a
555
+ // callable `then` after birth, and a computed key reaches it past any static spelling
556
+ // (`x["th" + "en"] = f`). But it applies to a RECORD and to nothing else, because the walker's
557
+ // order is: what kind of thing is being written (L4010), is it frozen (L2031), does that kind
558
+ // have this member (L4014/L4017/L4019) - and only then what the value is. MEASURED on the
559
+ // oracle: `keys({a:1}).then = () => 1` is L4014 "`then` is not a member of an array", the same
560
+ // answer as `.foo`, and `"x".then = f` and `(1).then = f` are L4010. Refusing L4018 here first
561
+ // answered the value's rule for a receiver that never had the member, which is a different
562
+ // sentence for the same program. On a record BOTH now refuse L4021: #657 gave the walker the
563
+ // rule at its record-member write sites, so what was the last declared divergence of this
564
+ // reordering is a corpus row the differential can compare.
565
+ if (prop === "then")
566
+ refuseThenable({ then: v }, "this field write");
567
+ setOwn(o, prop, v);
568
+ return v;
569
+ },
570
+ async call(o, k, args, optional, chain) {
571
+ // THE LOOKUP HAPPENS FIRST, AND `?.` DOES NOT SOFTEN IT. Measured on the walker: `xs.nope?.()`
572
+ // is L4014 exactly as `xs.nope()` is, and a member that resolves to a non-function is L4011.
573
+ // The only thing an optional call guards is a member that is null or undefined.
574
+ const found = lookup(o, keyOf(k), true);
575
+ // AN OPTIONAL CALL EVALUATES NO ARGUMENT WHEN IT SHORT-CIRCUITS, which is why the optional
576
+ // form takes a thunk: the transform evaluates arguments before it can call anything, so an
577
+ // array here would already have run them. Measured: the walker's short-circuit journalled
578
+ // nothing where the same argument on a present method journalled a `sleep`.
579
+ // A continuation without a short-circuit to guard is an emitter mistake: an ordinary call's
580
+ // chain is written natively, because nothing in it depends on a decision only the host made.
581
+ if (chain !== undefined && optional !== true) {
582
+ throw new RuntimeFault("L1000", "a call continuation belongs to an OPTIONAL call: there is nothing else for it to be skipped by");
583
+ }
584
+ if (optional === true && typeof args !== "function") {
585
+ throw new RuntimeFault("L1000", "an optional call must be handed its arguments as a thunk: it may not evaluate them at all, and an array is a list that has already been evaluated");
586
+ }
587
+ // Nothing runs: not the arguments, and not the rest of the chain. Measured on the walker, the
588
+ // short-circuit swallows a deep chain (`o.z?.().x.y`) and a trailing call alike.
589
+ if (found.from === "own" && (found.value === null || found.value === undefined) && optional === true) {
590
+ return undefined;
591
+ }
592
+ // AWAITED, because the thunk is `async`: every argument the transform emits may itself contain
593
+ // an `await`, so a sync arrow could not hold one. Measured without the await: an ordinary
594
+ // `o.m?.(1)` died on `Spread syntax requires ...iterable`, and `xs.map?.(f)` reached the
595
+ // curated method with a Promise where its argument list should be.
596
+ const list = typeof args === "function" ? await args() : args;
597
+ let answer;
598
+ if (found.from === "table") {
599
+ // A curated method: the walker's convention, and the ONE argument this method calls is
600
+ // adapted into it on the way in. Every other argument crosses untouched: see the invariant.
601
+ answer = await found.fn(currentFrame(), adaptArgs(found.key, list));
602
+ }
603
+ else if (typeof found.value !== "function") {
604
+ throw new RuntimeFault("L4011", "this value is not a function, so it cannot be called");
605
+ }
606
+ else {
607
+ // An own field holds a program-convention closure. Adapting here would pass the frame in as
608
+ // the first argument and shift every real one along by a position.
609
+ answer = await found.value(...list);
610
+ }
611
+ return chain === undefined ? answer : await chain(answer);
612
+ },
613
+ born(v) {
614
+ refuseThenable(v, "this value");
615
+ return stampBirth(v, currentFrame().depth);
616
+ },
617
+ async effect(name, args, site) {
618
+ const spec = PRIMITIVES[name];
619
+ if (spec === undefined)
620
+ throw new RuntimeFault("L2001", `${name} is not a primitive`);
621
+ if (spec.opensScope)
622
+ return await openScope(name, spec, args, site);
623
+ return await dispatchPrimitive(host, name, args, currentFrame());
624
+ },
625
+ free(name, args) {
626
+ const v = freeNames.get(name);
627
+ if (v === undefined && !freeNames.has(name)) {
628
+ throw new RuntimeFault("L2001", `${name} is not a builtin`);
629
+ }
630
+ // Read as a VALUE when no arguments are given: a builtin is a binding in this language, so
631
+ // `map(xs, upper)` and `const f = trim` both need one, and what leaves has to speak the
632
+ // program's convention or a native call would pass `(x)` where the library expects
633
+ // `(frame, [x])`.
634
+ if (args === undefined)
635
+ return toProgramValue(name, v);
636
+ if (typeof v !== "function") {
637
+ throw new RuntimeFault("L4011", `\`${name}\` is not a function, so it cannot be called`);
638
+ }
639
+ return v(currentFrame(), adaptArgs(`builtin.${name}`, args));
640
+ },
641
+ async await(v) {
642
+ // BEFORE the await, which is the whole point: once `await` has the value, the host has
643
+ // already called its `then`.
644
+ refuseThenable(v, "this awaited value");
645
+ return await v;
646
+ },
647
+ template(parts, values) {
648
+ let out = "";
649
+ for (let i = 0; i < parts.length; i += 1) {
650
+ out += parts[i] ?? "";
651
+ if (i < values.length) {
652
+ const v = values[i];
653
+ refuseCoercion("${...}", v);
654
+ out += String(v);
655
+ }
656
+ }
657
+ return out;
658
+ },
659
+ binary(op, l, r) {
660
+ const a = l;
661
+ const b = r;
662
+ switch (op) {
663
+ case "===":
664
+ return l === r;
665
+ case "!==":
666
+ return l !== r;
667
+ default:
668
+ break;
669
+ }
670
+ refuseCoercion(op, l);
671
+ refuseCoercion(op, r);
672
+ switch (op) {
673
+ case "<":
674
+ return a < b;
675
+ case "<=":
676
+ return a <= b;
677
+ case ">":
678
+ return a > b;
679
+ case ">=":
680
+ return a >= b;
681
+ case "+":
682
+ return a + b;
683
+ case "-":
684
+ return a - b;
685
+ case "*":
686
+ return a * b;
687
+ case "/":
688
+ return a / b;
689
+ case "%":
690
+ return a % b;
691
+ case "**":
692
+ return a ** b;
693
+ case "&":
694
+ return a & b;
695
+ case "|":
696
+ return a | b;
697
+ case "^":
698
+ return a ^ b;
699
+ case "<<":
700
+ return a << b;
701
+ case ">>":
702
+ return a >> b;
703
+ case ">>>":
704
+ return a >>> b;
705
+ default:
706
+ throw new RuntimeFault("L1000", `unsupported operator ${op}`);
707
+ }
708
+ },
709
+ unary(op, v) {
710
+ switch (op) {
711
+ case "!":
712
+ return !v;
713
+ case "typeof":
714
+ return typeof v;
715
+ case "-":
716
+ refuseCoercion("-", v);
717
+ return -v;
718
+ case "+":
719
+ refuseCoercion("+", v);
720
+ return +v;
721
+ case "~":
722
+ refuseCoercion("~", v);
723
+ return ~v;
724
+ case "update":
725
+ // `x++`, `x--` and their compound cousins, on the slow path only: the transform emits a
726
+ // native increment when it can see the operand is a number. A DECLARED DIVERGENCE: the
727
+ // walker reads the operand through `Number(...)`, so `"5"++` answers 6 and a
728
+ // record settles as NaN, while `o + 1` and `x += 1` refuse on the very same values. That
729
+ // is the silent-coercion class, filed against the walker as issue #646, and it is not
730
+ // being built into the new engine for fidelity's sake.
731
+ if (typeof v !== "number") {
732
+ throw new RuntimeFault("L4018", `\`++\` and \`--\` count, and ${v === null ? "null" : Array.isArray(v) ? "an array" : `a ${typeof v}`} is not a number, so there is nothing to count. Nothing is converted for you here: parse it first (\`number(value)\`), or hold the counter in a number.`);
733
+ }
734
+ return v;
735
+ default:
736
+ throw new RuntimeFault("L1000", `unsupported unary operator ${String(op)}`);
737
+ }
738
+ },
739
+ iter(v) {
740
+ if (Array.isArray(v))
741
+ return v;
742
+ if (typeof v === "string")
743
+ return [...v];
744
+ throw new RuntimeFault("L4015", `${v === null ? "null" : typeof v === "object" ? "a record" : typeof v} is not iterable: only arrays and strings can be spread or looped over. For a record, iterate \`keys(record)\` or \`entries(record)\`.`);
745
+ },
746
+ callee(v) {
747
+ // Member 14, the last of the seam. The transform emits it behind a `typeof` so a real call
748
+ // stays a native call; this is only the refusal, and it is the walker's own words because the
749
+ // differential suite compares the message, not merely the code.
750
+ if (typeof v !== "function") {
751
+ throw new RuntimeFault("L4011", `this value is not a function, so it cannot be called`);
752
+ }
753
+ return v;
754
+ },
755
+ caught(e) {
756
+ // The run's continuation is forfeit for these, and that includes its cleanup: the world-side
757
+ // recovery belongs to the driver and the journal, not to the program that just lost the right
758
+ // to run. They are recognised by CLASS, which is why the emitted catch has to ask rather than
759
+ // test a shape the program could forge.
760
+ if (isUncatchable(e))
761
+ throw e;
762
+ // A NATIVE ReferenceError IS THE ENGINE'S FAULT, NOT THE PROGRAM'S. The emitted module is
763
+ // closed over the seam with ZERO free identifiers, so nothing in it can name a binding that
764
+ // does not exist: a ReferenceError here can only be a TDZ read the transform's classifier
765
+ // missed, or an emitter temporary used before it was bound. Converting it would hand the
766
+ // program a `{code: "L4000"}` for a compiler bug, and MAPPING it to L2004 by reading its
767
+ // message would be worse - it would make the two indistinguishable exactly where they must not
768
+ // be. So it refuses loudly and uncatchably, with the original message carried along.
769
+ if (e instanceof ReferenceError)
770
+ throw new EngineFault(e);
771
+ return toProgramError(e);
772
+ },
773
+ };
774
+ return Object.defineProperty(ctx, STEPS, {
775
+ value: () => steps,
776
+ enumerable: false,
777
+ writable: false,
778
+ configurable: false,
779
+ });
780
+ }
781
+ // ---- the classes a program may not catch, and the value one that it may -------------------------
782
+ /**
783
+ * The engine broke its own contract, and no program may catch that.
784
+ *
785
+ * The one thing that reaches this today is a native `ReferenceError`. The emitted module is a closed
786
+ * expression with zero free identifiers, so nothing the program wrote can name a binding that is not
787
+ * there; a ReferenceError therefore means a TDZ read the transform's classifier missed or an emitter
788
+ * temporary read before it was bound. It is not the program's error, it is not converted to one, and
789
+ * it is never mapped to L2004 by reading its message - that mapping would make a compiler bug
790
+ * indistinguishable from the language rule it imitates.
791
+ */
792
+ export class EngineFault extends Error {
793
+ cause;
794
+ constructor(cause) {
795
+ super(`the engine broke its own contract: ${cause?.message ?? String(cause)}. The emitted module has zero free identifiers, so this can only be a binding the transform did not classify as a cell, or an emitter temporary read before it was bound. It is not the program's error and is not converted into one.`);
796
+ this.cause = cause;
797
+ this.name = "EngineFault";
798
+ }
799
+ }
800
+ /**
801
+ * Recognised by class rather than by shape.
802
+ *
803
+ * `Cancelled` and `RunReleased` come from effects.ts, `JournalAppendRejected` from journal.ts. The
804
+ * three the walker adds (`RunDivergence`, `ScopeBranchMissing`, `UnwalkableScope`) are exported by
805
+ * interpret.ts, and importing them here would pull the whole walker into the engine's module graph
806
+ * for three constructors. They are matched by `name` instead, which is a deliberate, narrow
807
+ * exception to "recognise by class": the names are the classes' own, they are set in the
808
+ * constructors, and a program cannot mint an Error subclass to forge one (there is no `Error` in the
809
+ * language). The differential suite carries a cell per class.
810
+ */
811
+ const UNCATCHABLE_NAMES = new Set([
812
+ "EngineFault",
813
+ "Cancelled",
814
+ "JournalAppendRejected",
815
+ "RunReleased",
816
+ "RunDivergence",
817
+ "ScopeBranchMissing",
818
+ "UnwalkableScope",
819
+ ]);
820
+ function isUncatchable(e) {
821
+ return e instanceof Error && UNCATCHABLE_NAMES.has(e.name);
822
+ }
823
+ /** What the catch parameter binds to: a frozen record with a code, never the host's own object. */
824
+ function toProgramError(e) {
825
+ if (e instanceof EffectError) {
826
+ return deepFreeze({ code: e.code, kind: e.kind, message: e.message, ...(e.detail !== undefined ? { detail: e.detail } : {}) });
827
+ }
828
+ if (e instanceof RuntimeFault)
829
+ return deepFreeze({ code: e.code, kind: "runtime", message: e.message });
830
+ if (e instanceof Error)
831
+ return deepFreeze({ code: "L4000", kind: "host", message: e.message });
832
+ return e;
833
+ }
834
+ //# sourceMappingURL=ctx.js.map