mustardscript 0.1.0 → 0.1.2

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 (96) hide show
  1. package/README.md +65 -22
  2. package/SECURITY.md +1 -1
  3. package/dist/index.js +2 -0
  4. package/dist/lib/executor.js +16 -1
  5. package/dist/lib/policy.js +301 -22
  6. package/dist/lib/progress.js +499 -113
  7. package/dist/lib/runtime.js +109 -40
  8. package/dist/lib/structured.js +327 -11
  9. package/dist/native-loader.js +11 -12
  10. package/index.d.ts +54 -6
  11. package/mustard.d.ts +23 -1
  12. package/package.json +34 -25
  13. package/Cargo.lock +0 -1579
  14. package/Cargo.toml +0 -40
  15. package/crates/mustard/Cargo.toml +0 -31
  16. package/crates/mustard/src/cancellation.rs +0 -28
  17. package/crates/mustard/src/diagnostic.rs +0 -145
  18. package/crates/mustard/src/ir.rs +0 -435
  19. package/crates/mustard/src/lib.rs +0 -21
  20. package/crates/mustard/src/limits.rs +0 -22
  21. package/crates/mustard/src/parser/expressions.rs +0 -723
  22. package/crates/mustard/src/parser/mod.rs +0 -115
  23. package/crates/mustard/src/parser/operators.rs +0 -105
  24. package/crates/mustard/src/parser/patterns.rs +0 -123
  25. package/crates/mustard/src/parser/scope.rs +0 -107
  26. package/crates/mustard/src/parser/statements.rs +0 -298
  27. package/crates/mustard/src/parser/tests/acceptance.rs +0 -339
  28. package/crates/mustard/src/parser/tests/mod.rs +0 -2
  29. package/crates/mustard/src/parser/tests/rejections.rs +0 -107
  30. package/crates/mustard/src/runtime/accounting.rs +0 -613
  31. package/crates/mustard/src/runtime/api.rs +0 -192
  32. package/crates/mustard/src/runtime/async_runtime/mod.rs +0 -5
  33. package/crates/mustard/src/runtime/async_runtime/promises.rs +0 -246
  34. package/crates/mustard/src/runtime/async_runtime/reactions.rs +0 -400
  35. package/crates/mustard/src/runtime/async_runtime/scheduler.rs +0 -224
  36. package/crates/mustard/src/runtime/builtins/arrays.rs +0 -1205
  37. package/crates/mustard/src/runtime/builtins/collections.rs +0 -573
  38. package/crates/mustard/src/runtime/builtins/install.rs +0 -501
  39. package/crates/mustard/src/runtime/builtins/intl.rs +0 -553
  40. package/crates/mustard/src/runtime/builtins/mod.rs +0 -25
  41. package/crates/mustard/src/runtime/builtins/objects.rs +0 -405
  42. package/crates/mustard/src/runtime/builtins/primitives.rs +0 -859
  43. package/crates/mustard/src/runtime/builtins/promises.rs +0 -335
  44. package/crates/mustard/src/runtime/builtins/regexp.rs +0 -356
  45. package/crates/mustard/src/runtime/builtins/strings.rs +0 -803
  46. package/crates/mustard/src/runtime/builtins/support.rs +0 -561
  47. package/crates/mustard/src/runtime/bytecode.rs +0 -123
  48. package/crates/mustard/src/runtime/compiler/assignments.rs +0 -690
  49. package/crates/mustard/src/runtime/compiler/bindings.rs +0 -92
  50. package/crates/mustard/src/runtime/compiler/context.rs +0 -46
  51. package/crates/mustard/src/runtime/compiler/control.rs +0 -342
  52. package/crates/mustard/src/runtime/compiler/expressions.rs +0 -372
  53. package/crates/mustard/src/runtime/compiler/mod.rs +0 -173
  54. package/crates/mustard/src/runtime/compiler/statements.rs +0 -459
  55. package/crates/mustard/src/runtime/conversions/boundary.rs +0 -293
  56. package/crates/mustard/src/runtime/conversions/coercions.rs +0 -217
  57. package/crates/mustard/src/runtime/conversions/errors.rs +0 -118
  58. package/crates/mustard/src/runtime/conversions/mod.rs +0 -14
  59. package/crates/mustard/src/runtime/conversions/operators.rs +0 -334
  60. package/crates/mustard/src/runtime/env.rs +0 -355
  61. package/crates/mustard/src/runtime/exceptions.rs +0 -377
  62. package/crates/mustard/src/runtime/gc.rs +0 -595
  63. package/crates/mustard/src/runtime/mod.rs +0 -318
  64. package/crates/mustard/src/runtime/properties.rs +0 -1762
  65. package/crates/mustard/src/runtime/serialization.rs +0 -127
  66. package/crates/mustard/src/runtime/shared.rs +0 -108
  67. package/crates/mustard/src/runtime/snapshot_validation_tests.rs +0 -93
  68. package/crates/mustard/src/runtime/state.rs +0 -652
  69. package/crates/mustard/src/runtime/tests/async_host.rs +0 -104
  70. package/crates/mustard/src/runtime/tests/collections.rs +0 -50
  71. package/crates/mustard/src/runtime/tests/diagnostics.rs +0 -36
  72. package/crates/mustard/src/runtime/tests/exceptions.rs +0 -122
  73. package/crates/mustard/src/runtime/tests/execution.rs +0 -553
  74. package/crates/mustard/src/runtime/tests/gc.rs +0 -533
  75. package/crates/mustard/src/runtime/tests/mod.rs +0 -56
  76. package/crates/mustard/src/runtime/tests/serialization.rs +0 -170
  77. package/crates/mustard/src/runtime/validation/bytecode.rs +0 -484
  78. package/crates/mustard/src/runtime/validation/mod.rs +0 -14
  79. package/crates/mustard/src/runtime/validation/policy.rs +0 -94
  80. package/crates/mustard/src/runtime/validation/snapshot.rs +0 -406
  81. package/crates/mustard/src/runtime/validation/walk.rs +0 -206
  82. package/crates/mustard/src/runtime/vm.rs +0 -1016
  83. package/crates/mustard/src/span.rs +0 -22
  84. package/crates/mustard/src/structured.rs +0 -107
  85. package/crates/mustard-bridge/Cargo.toml +0 -17
  86. package/crates/mustard-bridge/src/codec.rs +0 -46
  87. package/crates/mustard-bridge/src/dto.rs +0 -99
  88. package/crates/mustard-bridge/src/lib.rs +0 -12
  89. package/crates/mustard-bridge/src/operations.rs +0 -142
  90. package/crates/mustard-node/Cargo.toml +0 -24
  91. package/crates/mustard-node/build.rs +0 -3
  92. package/crates/mustard-node/src/lib.rs +0 -236
  93. package/crates/mustard-sidecar/Cargo.toml +0 -21
  94. package/crates/mustard-sidecar/src/lib.rs +0 -134
  95. package/crates/mustard-sidecar/src/main.rs +0 -36
  96. package/dist/install.js +0 -117
@@ -8,19 +8,23 @@ const { performance } = require('node:perf_hooks');
8
8
  const { MustardError, callNative } = require('./errors.js');
9
9
  const { getAbortSignal, withCancellationSignal } = require('./cancellation.js');
10
10
  const {
11
+ assertSuspendedManifest,
11
12
  cloneSnapshotPolicy,
12
13
  cloneSnapshotKey,
14
+ createSuspendedManifest,
13
15
  encodeSnapshotPolicy,
16
+ programIdentity,
14
17
  resolveProgressLoadContext,
15
18
  snapshotIdentity,
16
19
  snapshotKeyDigest,
17
20
  snapshotToken,
21
+ suspendedManifestToken,
18
22
  } = require('./policy.js');
19
23
  const {
20
24
  decodeStructured,
21
- encodeResumePayloadCancel,
22
- encodeResumePayloadError,
23
- encodeResumePayloadValue,
25
+ encodeResumePayloadCancelBuffer,
26
+ encodeResumePayloadErrorBuffer,
27
+ encodeResumePayloadValueBuffer,
24
28
  } = require('./structured.js');
25
29
 
26
30
  const SHARED_PROGRESS_REGISTRY_ROOT = path.join(
@@ -74,44 +78,63 @@ function singleUseRuntimeError() {
74
78
  }
75
79
 
76
80
  function releaseClaimedSnapshot(native, snapshotIdentityValue) {
77
- try {
78
- callNative(native.releaseProgressSnapshot, snapshotIdentityValue);
79
- } finally {
80
- releaseSharedProgressSnapshot(snapshotIdentityValue);
81
- }
81
+ void native;
82
+ releaseSharedProgressSnapshot(snapshotIdentityValue);
82
83
  }
83
84
 
84
85
  function claimSnapshotForLoad(native, snapshotIdentityValue) {
86
+ void native;
85
87
  if (!claimSharedProgressSnapshot(snapshotIdentityValue)) {
86
88
  throw singleUseRuntimeError();
87
89
  }
88
- try {
89
- if (!callNative(native.claimProgressSnapshot, snapshotIdentityValue)) {
90
- releaseSharedProgressSnapshot(snapshotIdentityValue);
91
- throw singleUseRuntimeError();
92
- }
93
- } catch (error) {
94
- if (!(error instanceof MustardError)) {
95
- releaseSharedProgressSnapshot(snapshotIdentityValue);
96
- }
97
- throw error;
98
- }
99
-
100
90
  return () => {
101
91
  releaseClaimedSnapshot(native, snapshotIdentityValue);
102
92
  };
103
93
  }
104
94
 
105
95
  function assertSnapshotNotUsed(native, snapshotIdentityValue) {
106
- if (
107
- isSharedProgressSnapshotUsed(snapshotIdentityValue) ||
108
- callNative(native.isProgressSnapshotUsed, snapshotIdentityValue)
109
- ) {
96
+ void native;
97
+ if (isSharedProgressSnapshotUsed(snapshotIdentityValue)) {
110
98
  throw singleUseRuntimeError();
111
99
  }
112
100
  }
113
101
 
102
+ function isBinaryLike(value) {
103
+ return Buffer.isBuffer(value) || value instanceof Uint8Array;
104
+ }
105
+
114
106
  function createProgressApi(native) {
107
+ const programHandleRegistry =
108
+ typeof FinalizationRegistry === 'function'
109
+ ? new FinalizationRegistry((programHandle) => {
110
+ try {
111
+ callNative(native.releaseProgram, programHandle);
112
+ } catch {
113
+ // Best-effort cleanup only; process shutdown can race native teardown.
114
+ }
115
+ })
116
+ : null;
117
+ const snapshotHandleRegistry =
118
+ typeof FinalizationRegistry === 'function'
119
+ ? new FinalizationRegistry((snapshotHandle) => {
120
+ try {
121
+ callNative(native.releaseSnapshotHandle, snapshotHandle);
122
+ } catch {
123
+ // Best-effort cleanup only; process shutdown can race native teardown.
124
+ }
125
+ })
126
+ : null;
127
+
128
+ function assertAuthorizedSuspendedCapability(policy, capability) {
129
+ if (policy.capabilities.includes(capability)) {
130
+ return;
131
+ }
132
+ throw new MustardError(
133
+ 'Serialization',
134
+ `snapshot policy rejected unauthorized capability \`${capability}\``,
135
+ );
136
+ }
137
+
115
138
  class Progress {
116
139
  constructor(
117
140
  snapshot,
@@ -121,25 +144,173 @@ function createProgressApi(native) {
121
144
  snapshotKey,
122
145
  token = undefined,
123
146
  claimState = 'unclaimed',
147
+ suspendedManifest = undefined,
148
+ suspendedManifestTokenValue = undefined,
149
+ programHandle = null,
150
+ program = undefined,
151
+ programId = undefined,
152
+ snapshotHandle = null,
153
+ snapshotId = undefined,
154
+ snapshotKeyBase64 = undefined,
155
+ snapshotKeyDigestValue = undefined,
124
156
  ) {
125
- this.capability = capability;
126
- this.args = args;
127
- this.#snapshot = Buffer.from(snapshot);
128
- this.#snapshotIdentity = snapshotIdentity(this.#snapshot);
157
+ this.#capability = capability;
158
+ this.#args = structuredClone(args);
159
+ this.capability = this.#capability;
160
+ this.args = structuredClone(this.#args);
161
+ this.#snapshot =
162
+ snapshot === undefined || snapshot === null ? null : Buffer.from(snapshot);
163
+ this.#snapshotIdentity =
164
+ typeof snapshotId === 'string' && snapshotId.length > 0
165
+ ? snapshotId
166
+ : this.#snapshot !== null
167
+ ? snapshotIdentity(this.#snapshot)
168
+ : null;
129
169
  this.#snapshotKey = cloneSnapshotKey(snapshotKey);
130
- this.#snapshotKeyDigest = snapshotKeyDigest(this.#snapshotKey);
131
- this.#snapshotToken = token ?? snapshotToken(this.#snapshot, this.#snapshotKey);
170
+ this.#snapshotKeyBase64 =
171
+ typeof snapshotKeyBase64 === 'string' && snapshotKeyBase64.length > 0
172
+ ? snapshotKeyBase64
173
+ : this.#snapshotKey.toString('base64');
174
+ this.#snapshotKeyDigest =
175
+ typeof snapshotKeyDigestValue === 'string' && snapshotKeyDigestValue.length > 0
176
+ ? snapshotKeyDigestValue
177
+ : snapshotKeyDigest(this.#snapshotKey);
178
+ this.#snapshotToken =
179
+ typeof token === 'string' && token.length > 0
180
+ ? token
181
+ : this.#snapshot !== null
182
+ ? snapshotToken(this.#snapshot, this.#snapshotKey, this.#snapshotIdentity)
183
+ : null;
184
+ this.#suspendedManifest =
185
+ suspendedManifest ?? createSuspendedManifest(this.#capability, this.#args);
186
+ this.#suspendedManifestToken =
187
+ typeof suspendedManifestTokenValue === 'string' && suspendedManifestTokenValue.length > 0
188
+ ? suspendedManifestTokenValue
189
+ : this.#snapshotIdentity === null
190
+ ? null
191
+ : suspendedManifestToken(
192
+ this.#snapshotIdentity,
193
+ this.#suspendedManifest,
194
+ this.#snapshotKey,
195
+ );
132
196
  this.#policy = cloneSnapshotPolicy(policy);
197
+ this.#snapshotPolicyJson =
198
+ this.#snapshotIdentity === null || this.#snapshotToken === null
199
+ ? null
200
+ : encodeSnapshotPolicy(this.#policy, {
201
+ snapshotId: this.#snapshotIdentity,
202
+ snapshotKeyBase64: this.#snapshotKeyBase64,
203
+ snapshotKeyDigest: this.#snapshotKeyDigest,
204
+ snapshotToken: this.#snapshotToken,
205
+ });
133
206
  this.#claimState = claimState;
207
+ this.#program = program === undefined || program === null ? null : Buffer.from(program);
208
+ this.#programIdentity =
209
+ typeof programId === 'string' && programId.length > 0
210
+ ? programId
211
+ : this.#program !== null
212
+ ? programIdentity(this.#program)
213
+ : null;
214
+ this.#programHandle = null;
215
+ this.#programHandleToken = null;
216
+ if (typeof programHandle === 'string' && programHandle.length > 0) {
217
+ this.#programHandle = programHandle;
218
+ this.#programHandleToken = {};
219
+ programHandleRegistry?.register(this, programHandle, this.#programHandleToken);
220
+ }
221
+ this.#snapshotHandle = null;
222
+ this.#snapshotHandleToken = null;
223
+ if (typeof snapshotHandle === 'string' && snapshotHandle.length > 0) {
224
+ this.#snapshotHandle = snapshotHandle;
225
+ this.#snapshotHandleToken = {};
226
+ snapshotHandleRegistry?.register(this, snapshotHandle, this.#snapshotHandleToken);
227
+ }
134
228
  }
135
229
 
230
+ #capability;
231
+ #args;
136
232
  #snapshot;
137
233
  #snapshotIdentity;
138
234
  #snapshotKey;
235
+ #snapshotKeyBase64;
139
236
  #snapshotKeyDigest;
140
237
  #snapshotToken;
238
+ #suspendedManifest;
239
+ #suspendedManifestToken;
141
240
  #policy;
241
+ #snapshotPolicyJson;
142
242
  #claimState;
243
+ #program;
244
+ #programIdentity;
245
+ #programHandle;
246
+ #programHandleToken;
247
+ #snapshotHandle;
248
+ #snapshotHandleToken;
249
+
250
+ #clearSnapshotHandle() {
251
+ if (this.#snapshotHandleToken !== null) {
252
+ snapshotHandleRegistry?.unregister(this.#snapshotHandleToken);
253
+ }
254
+ this.#snapshotHandle = null;
255
+ this.#snapshotHandleToken = null;
256
+ }
257
+
258
+ #releaseSnapshotHandle() {
259
+ if (this.#snapshotHandle === null) {
260
+ return;
261
+ }
262
+ const snapshotHandle = this.#snapshotHandle;
263
+ this.#clearSnapshotHandle();
264
+ try {
265
+ callNative(native.releaseSnapshotHandle, snapshotHandle);
266
+ } catch {
267
+ // Best-effort cleanup only.
268
+ }
269
+ }
270
+
271
+ #ensureSnapshotBytes() {
272
+ if (this.#snapshot !== null) {
273
+ return Buffer.from(this.#snapshot);
274
+ }
275
+ if (this.#snapshotHandle === null) {
276
+ throw singleUseRuntimeError();
277
+ }
278
+ this.#snapshot = Buffer.from(callNative(native.dumpSnapshotHandle, this.#snapshotHandle));
279
+ this.#snapshotIdentity ??= snapshotIdentity(this.#snapshot);
280
+ this.#snapshotToken ??= snapshotToken(
281
+ this.#snapshot,
282
+ this.#snapshotKey,
283
+ this.#snapshotIdentity,
284
+ );
285
+ this.#suspendedManifestToken ??= suspendedManifestToken(
286
+ this.#snapshotIdentity,
287
+ this.#suspendedManifest,
288
+ this.#snapshotKey,
289
+ );
290
+ this.#snapshotPolicyJson ??= encodeSnapshotPolicy(this.#policy, {
291
+ snapshotId: this.#snapshotIdentity,
292
+ snapshotKeyBase64: this.#snapshotKeyBase64,
293
+ snapshotKeyDigest: this.#snapshotKeyDigest,
294
+ snapshotToken: this.#snapshotToken,
295
+ });
296
+ return Buffer.from(this.#snapshot);
297
+ }
298
+
299
+ #ensureSnapshotPolicyJson() {
300
+ if (this.#snapshotPolicyJson !== null) {
301
+ return this.#snapshotPolicyJson;
302
+ }
303
+ const snapshot = this.#ensureSnapshotBytes();
304
+ this.#snapshotIdentity ??= snapshotIdentity(snapshot);
305
+ this.#snapshotToken ??= snapshotToken(snapshot, this.#snapshotKey, this.#snapshotIdentity);
306
+ this.#snapshotPolicyJson = encodeSnapshotPolicy(this.#policy, {
307
+ snapshotId: this.#snapshotIdentity,
308
+ snapshotKeyBase64: this.#snapshotKeyBase64,
309
+ snapshotKeyDigest: this.#snapshotKeyDigest,
310
+ snapshotToken: this.#snapshotToken,
311
+ });
312
+ return this.#snapshotPolicyJson;
313
+ }
143
314
 
144
315
  #consumeSnapshot() {
145
316
  if (this.#claimState === 'consumed') {
@@ -152,34 +323,127 @@ function createProgressApi(native) {
152
323
  if (!claimSharedProgressSnapshot(this.#snapshotIdentity)) {
153
324
  throw singleUseRuntimeError();
154
325
  }
155
- try {
156
- if (!callNative(native.claimProgressSnapshot, this.#snapshotIdentity)) {
157
- releaseSharedProgressSnapshot(this.#snapshotIdentity);
158
- throw singleUseRuntimeError();
159
- }
160
- } catch (error) {
161
- if (!(error instanceof MustardError)) {
162
- releaseSharedProgressSnapshot(this.#snapshotIdentity);
163
- }
164
- throw error;
165
- }
166
326
  this.#claimState = 'consumed';
167
327
  return Buffer.from(this.#snapshot);
168
328
  }
169
329
 
330
+ #consumeSnapshotHandle() {
331
+ if (this.#claimState === 'consumed') {
332
+ throw singleUseRuntimeError();
333
+ }
334
+ if (
335
+ this.#claimState === 'unclaimed' &&
336
+ this.#snapshotIdentity !== null &&
337
+ !claimSharedProgressSnapshot(this.#snapshotIdentity)
338
+ ) {
339
+ throw singleUseRuntimeError();
340
+ }
341
+ if (this.#snapshotHandle === null) {
342
+ return null;
343
+ }
344
+ const snapshotHandle = this.#snapshotHandle;
345
+ this.#clearSnapshotHandle();
346
+ this.#claimState = 'consumed';
347
+ return snapshotHandle;
348
+ }
349
+
350
+ #ensureProgramHandle() {
351
+ if (this.#programHandle !== null) {
352
+ return this.#programHandle;
353
+ }
354
+ if (this.#program === null) {
355
+ return null;
356
+ }
357
+ const programHandle = callNative(native.loadProgram, Buffer.from(this.#program));
358
+ this.#programHandle = programHandle;
359
+ this.#programHandleToken = {};
360
+ programHandleRegistry?.register(this, programHandle, this.#programHandleToken);
361
+ return programHandle;
362
+ }
363
+
364
+ #ensureProgramBytes() {
365
+ if (this.#program !== null) {
366
+ return Buffer.from(this.#program);
367
+ }
368
+ if (this.#programHandle === null) {
369
+ return null;
370
+ }
371
+ this.#program = Buffer.from(callNative(native.dumpProgram, this.#programHandle));
372
+ this.#programIdentity ??= programIdentity(this.#program);
373
+ return Buffer.from(this.#program);
374
+ }
375
+
376
+ #resumeWithPayload(payload, signal) {
377
+ const programHandle = this.#ensureProgramHandle();
378
+ const snapshotHandle = this.#consumeSnapshotHandle();
379
+ if (snapshotHandle !== null) {
380
+ try {
381
+ const nativeArgs = [snapshotHandle, payload];
382
+ const step = parseStep(
383
+ signal === undefined
384
+ ? callNative(native.resumeSnapshotHandleBuffer, ...nativeArgs)
385
+ : withCancellationSignal(
386
+ native,
387
+ native.resumeSnapshotHandleBuffer,
388
+ nativeArgs,
389
+ signal,
390
+ ),
391
+ );
392
+ return materializeStep(step, this.#policy, this.#snapshotKey, programHandle, this.#program);
393
+ } finally {
394
+ try {
395
+ callNative(native.releaseSnapshotHandle, snapshotHandle);
396
+ } catch {
397
+ // Best-effort cleanup only.
398
+ }
399
+ }
400
+ }
401
+
402
+ const snapshot = this.#consumeSnapshot();
403
+ const policyJson = this.#ensureSnapshotPolicyJson();
404
+ const nativeResume =
405
+ programHandle === null ? native.resumeProgram : native.resumeDetachedProgram;
406
+ const nativeArgs =
407
+ programHandle === null
408
+ ? [snapshot, payload, policyJson]
409
+ : [programHandle, snapshot, payload, policyJson];
410
+ const step = parseStep(
411
+ signal === undefined
412
+ ? callNative(nativeResume, ...nativeArgs)
413
+ : withCancellationSignal(native, nativeResume, nativeArgs, signal),
414
+ );
415
+ return materializeStep(step, this.#policy, this.#snapshotKey, programHandle, this.#program);
416
+ }
417
+
170
418
  get snapshot() {
171
- return Buffer.from(this.#snapshot);
419
+ return this.#ensureSnapshotBytes();
172
420
  }
173
421
 
174
422
  dump() {
175
- return {
176
- capability: this.capability,
177
- args: this.args.slice(),
178
- snapshot: this.snapshot,
423
+ const snapshot = this.#ensureSnapshotBytes();
424
+ this.#snapshotIdentity ??= snapshotIdentity(snapshot);
425
+ this.#snapshotToken ??= snapshotToken(snapshot, this.#snapshotKey, this.#snapshotIdentity);
426
+ this.#suspendedManifestToken ??= suspendedManifestToken(
427
+ this.#snapshotIdentity,
428
+ this.#suspendedManifest,
429
+ this.#snapshotKey,
430
+ );
431
+ const dumped = {
432
+ capability: this.#capability,
433
+ args: structuredClone(this.#args),
434
+ snapshot,
179
435
  snapshot_id: this.#snapshotIdentity,
180
436
  snapshot_key_digest: this.#snapshotKeyDigest,
181
437
  token: this.#snapshotToken,
438
+ suspended_manifest: this.#suspendedManifest,
439
+ suspended_manifest_token: this.#suspendedManifestToken,
182
440
  };
441
+ const program = this.#ensureProgramBytes();
442
+ if (program !== null) {
443
+ dumped.program = program;
444
+ dumped.program_id = this.#programIdentity ?? programIdentity(program);
445
+ }
446
+ return dumped;
183
447
  }
184
448
 
185
449
  resume(value, options = undefined) {
@@ -187,21 +451,7 @@ function createProgressApi(native) {
187
451
  if (signal?.aborted) {
188
452
  return this.cancel();
189
453
  }
190
- const payload = encodeResumePayloadValue(value);
191
- const policyJson = encodeSnapshotPolicy(this.#policy, {
192
- snapshotId: this.#snapshotIdentity,
193
- snapshotKey: this.#snapshotKey,
194
- snapshotToken: this.#snapshotToken,
195
- });
196
- const step = parseStep(
197
- withCancellationSignal(
198
- native,
199
- native.resumeProgram,
200
- [this.#consumeSnapshot(), payload, policyJson],
201
- signal,
202
- ),
203
- );
204
- return materializeStep(step, this.#policy, this.#snapshotKey);
454
+ return this.#resumeWithPayload(encodeResumePayloadValueBuffer(value), signal);
205
455
  }
206
456
 
207
457
  resumeError(error, options = undefined) {
@@ -209,38 +459,11 @@ function createProgressApi(native) {
209
459
  if (signal?.aborted) {
210
460
  return this.cancel();
211
461
  }
212
- const payload = encodeResumePayloadError(error);
213
- const policyJson = encodeSnapshotPolicy(this.#policy, {
214
- snapshotId: this.#snapshotIdentity,
215
- snapshotKey: this.#snapshotKey,
216
- snapshotToken: this.#snapshotToken,
217
- });
218
- const step = parseStep(
219
- withCancellationSignal(
220
- native,
221
- native.resumeProgram,
222
- [this.#consumeSnapshot(), payload, policyJson],
223
- signal,
224
- ),
225
- );
226
- return materializeStep(step, this.#policy, this.#snapshotKey);
462
+ return this.#resumeWithPayload(encodeResumePayloadErrorBuffer(error), signal);
227
463
  }
228
464
 
229
465
  cancel() {
230
- const policyJson = encodeSnapshotPolicy(this.#policy, {
231
- snapshotId: this.#snapshotIdentity,
232
- snapshotKey: this.#snapshotKey,
233
- snapshotToken: this.#snapshotToken,
234
- });
235
- const step = parseStep(
236
- callNative(
237
- native.resumeProgram,
238
- this.#consumeSnapshot(),
239
- encodeResumePayloadCancel(),
240
- policyJson,
241
- ),
242
- );
243
- return materializeStep(step, this.#policy, this.#snapshotKey);
466
+ return this.#resumeWithPayload(encodeResumePayloadCancelBuffer(), undefined);
244
467
  }
245
468
 
246
469
  static load(state, options = undefined) {
@@ -282,30 +505,168 @@ function createProgressApi(native) {
282
505
  'Progress.load() rejected a tampered or unauthenticated snapshot',
283
506
  );
284
507
  }
508
+
509
+ let dumpedProgram;
510
+ let dumpedProgramId;
511
+ if (state.program !== undefined) {
512
+ if (!isBinaryLike(state.program)) {
513
+ throw new TypeError('Progress.load() requires dumped program bytes as Buffer or Uint8Array');
514
+ }
515
+ if (typeof state.program_id !== 'string' || state.program_id.length === 0) {
516
+ throw new TypeError(
517
+ 'Progress.load() requires dumped program_id metadata when program bytes are present',
518
+ );
519
+ }
520
+ dumpedProgram = Buffer.from(state.program);
521
+ dumpedProgramId = programIdentity(dumpedProgram);
522
+ if (dumpedProgramId !== state.program_id) {
523
+ throw new MustardError(
524
+ 'Serialization',
525
+ 'Progress.load() rejected a tampered or mismatched detached program',
526
+ );
527
+ }
528
+ }
529
+
285
530
  assertSnapshotNotUsed(native, snapshotIdentityValue);
286
- const context = resolveProgressLoadContext(state, snapshot, options);
287
- const releaseClaim = claimSnapshotForLoad(native, snapshotIdentityValue);
288
- try {
289
- const inspection = parseSnapshotInspection(
290
- callNative(
291
- native.inspectSnapshot,
292
- snapshot,
293
- encodeSnapshotPolicy(context.policy, {
531
+ const context = resolveProgressLoadContext(
532
+ state,
533
+ snapshot,
534
+ options,
535
+ snapshotIdentityValue,
536
+ );
537
+ const suspendedManifest = assertSuspendedManifest(
538
+ state,
539
+ context.snapshotKey,
540
+ snapshotIdentityValue,
541
+ );
542
+ const nativeContextHandle =
543
+ typeof context.nativeContextHandle === 'string' && context.nativeContextHandle.length > 0
544
+ ? context.nativeContextHandle
545
+ : null;
546
+ const policyJson =
547
+ nativeContextHandle === null
548
+ ? encodeSnapshotPolicy(context.policy, {
294
549
  snapshotId: state.snapshot_id,
295
550
  snapshotKey: context.snapshotKey,
551
+ snapshotKeyBase64: context.snapshotKeyBase64,
552
+ snapshotKeyDigest: context.snapshotKeyDigest,
296
553
  snapshotToken: state.token,
297
- }),
298
- ),
299
- );
300
- return new Progress(
301
- snapshot,
302
- inspection.capability,
303
- inspection.args,
304
- context.policy,
305
- context.snapshotKey,
306
- state.token,
307
- 'claimed',
308
- );
554
+ })
555
+ : null;
556
+ const releaseClaim = claimSnapshotForLoad(native, snapshotIdentityValue);
557
+ try {
558
+ let loadedProgramHandle = null;
559
+ let snapshotHandle = null;
560
+ const loadSnapshotHandle = () => {
561
+ if (dumpedProgram === undefined) {
562
+ return nativeContextHandle === null
563
+ ? callNative(native.loadSnapshotHandle, snapshot, policyJson)
564
+ : callNative(
565
+ native.loadSnapshotHandleWithExecutionContext,
566
+ nativeContextHandle,
567
+ snapshot,
568
+ state.snapshot_id,
569
+ context.snapshotKeyBase64,
570
+ context.snapshotKeyDigest,
571
+ state.token,
572
+ );
573
+ }
574
+ loadedProgramHandle = callNative(native.loadProgram, Buffer.from(dumpedProgram));
575
+ return nativeContextHandle === null
576
+ ? callNative(
577
+ native.loadDetachedSnapshotHandle,
578
+ loadedProgramHandle,
579
+ snapshot,
580
+ policyJson,
581
+ )
582
+ : callNative(
583
+ native.loadDetachedSnapshotHandleWithExecutionContext,
584
+ loadedProgramHandle,
585
+ nativeContextHandle,
586
+ snapshot,
587
+ state.snapshot_id,
588
+ context.snapshotKeyBase64,
589
+ context.snapshotKeyDigest,
590
+ state.token,
591
+ );
592
+ };
593
+ if (suspendedManifest !== null) {
594
+ assertAuthorizedSuspendedCapability(
595
+ context.policy,
596
+ suspendedManifest.capability,
597
+ );
598
+ try {
599
+ snapshotHandle = loadSnapshotHandle();
600
+ } catch (error) {
601
+ if (loadedProgramHandle !== null) {
602
+ try {
603
+ callNative(native.releaseProgram, loadedProgramHandle);
604
+ } catch {
605
+ // Best-effort cleanup only.
606
+ }
607
+ }
608
+ throw error;
609
+ }
610
+ return new Progress(
611
+ snapshot,
612
+ suspendedManifest.capability,
613
+ suspendedManifest.args,
614
+ context.policy,
615
+ context.snapshotKey,
616
+ state.token,
617
+ 'claimed',
618
+ state.suspended_manifest,
619
+ state.suspended_manifest_token,
620
+ loadedProgramHandle,
621
+ dumpedProgram,
622
+ dumpedProgramId,
623
+ snapshotHandle,
624
+ state.snapshot_id,
625
+ context.snapshotKeyBase64,
626
+ context.snapshotKeyDigest,
627
+ );
628
+ }
629
+
630
+ try {
631
+ snapshotHandle = loadSnapshotHandle();
632
+ const inspection = parseSnapshotInspection(
633
+ callNative(native.inspectSnapshotHandle, snapshotHandle),
634
+ );
635
+ return new Progress(
636
+ snapshot,
637
+ inspection.capability,
638
+ inspection.args,
639
+ context.policy,
640
+ context.snapshotKey,
641
+ state.token,
642
+ 'claimed',
643
+ undefined,
644
+ undefined,
645
+ loadedProgramHandle,
646
+ dumpedProgram,
647
+ dumpedProgramId,
648
+ snapshotHandle,
649
+ state.snapshot_id,
650
+ context.snapshotKeyBase64,
651
+ context.snapshotKeyDigest,
652
+ );
653
+ } catch (error) {
654
+ if (snapshotHandle !== null) {
655
+ try {
656
+ callNative(native.releaseSnapshotHandle, snapshotHandle);
657
+ } catch {
658
+ // Best-effort cleanup only.
659
+ }
660
+ }
661
+ if (loadedProgramHandle !== null) {
662
+ try {
663
+ callNative(native.releaseProgram, loadedProgramHandle);
664
+ } catch {
665
+ // Best-effort cleanup only.
666
+ }
667
+ }
668
+ throw error;
669
+ }
309
670
  } catch (error) {
310
671
  releaseClaim();
311
672
  throw error;
@@ -325,7 +686,14 @@ function createProgressApi(native) {
325
686
  type: 'suspended',
326
687
  capability: step.capability,
327
688
  args: step.args.map(decodeStructured),
328
- snapshot: Buffer.from(step.snapshot_base64, 'base64'),
689
+ snapshot:
690
+ typeof step.snapshot_base64 === 'string'
691
+ ? Buffer.from(step.snapshot_base64, 'base64')
692
+ : null,
693
+ snapshotHandle:
694
+ typeof step.snapshot_handle === 'string' && step.snapshot_handle.length > 0
695
+ ? step.snapshot_handle
696
+ : null,
329
697
  };
330
698
  }
331
699
 
@@ -337,11 +705,29 @@ function createProgressApi(native) {
337
705
  };
338
706
  }
339
707
 
340
- function materializeStep(step, policy, snapshotKey) {
708
+ function materializeStep(step, policy, snapshotKey, programHandle = null, program = undefined) {
341
709
  if (step.type === 'completed') {
342
710
  return step.value;
343
711
  }
344
- return new Progress(step.snapshot, step.capability, step.args, policy, snapshotKey);
712
+ let ownedProgramHandle = null;
713
+ if (typeof programHandle === 'string' && programHandle.length > 0) {
714
+ ownedProgramHandle = callNative(native.retainProgram, programHandle);
715
+ }
716
+ return new Progress(
717
+ step.snapshot,
718
+ step.capability,
719
+ step.args,
720
+ policy,
721
+ snapshotKey,
722
+ undefined,
723
+ 'unclaimed',
724
+ undefined,
725
+ undefined,
726
+ ownedProgramHandle,
727
+ program,
728
+ undefined,
729
+ step.snapshotHandle,
730
+ );
345
731
  }
346
732
 
347
733
  return {