@hew-lang/sandbox-vm 0.10.0 → 0.11.0-rc3

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.
package/README.md CHANGED
@@ -61,6 +61,23 @@ A `sandbox_rejected` result includes rejection entries with `category`, `code`,
61
61
  checked traps, VM failures and step-budget exhaustion have distinct statuses.
62
62
  Consumers can offer explicit remote execution for native capabilities.
63
63
 
64
+ ## Crash details
65
+
66
+ Actor callers and crash hooks receive the same originating `CrashInfo`, with
67
+ owned kind, trap, message, site and trace fields. The VM captures up to 48 real
68
+ Hew activations before unwind and folds consecutive identical functions into
69
+ `StackFrame.repeat`. Distinct instruction sites remain separate. Diagnostic
70
+ messages are bounded to 4096 UTF-8 bytes, including an explicit ` [truncated]`
71
+ suffix when needed; oversized function names are omitted. Capture is always enabled; there is no VM trace-control
72
+ option. Frames describe the failing execution rather than its observer.
73
+
74
+ Current bytecode carries function names and byte-offset spans, but no source
75
+ file and line table. `CrashInfo.site` and each frame's `site` are therefore
76
+ `None`. A failure outside an executing Hew activation also has no trace.
77
+ Diagnostic materialization respects the receiving actor's managed heap budget:
78
+ it omits the trace first, then uses an empty message if necessary, preserving
79
+ the original kind and trap when diagnostic storage cannot fit.
80
+
64
81
  ## Validation
65
82
 
66
83
  From the repository root:
@@ -21,6 +21,7 @@ import { Semaphores } from "./semaphores.js";
21
21
  import { FrameScheduler } from "./scheduler.js";
22
22
  import { DeterministicIds } from "../../scheduler/ids.js";
23
23
  import { Mt19937 } from "./mt19937.js";
24
+ import { FAILURE_TEXT_LIMIT, FAILURE_TRUNCATED_SUFFIX, } from "./failure-limits.generated.js";
24
25
  import { ShimFault, CODEC_REGISTRATION, decodeUtf8Checked, resolveExtern, resolveRuntime, resolveSuspension, } from "./shims.js";
25
26
  import { AdmittedPackage } from "./validate.js";
26
27
  import { HeapExceeded, ManagedBudget, ManagedLayouts, } from "./managed-budget.js";
@@ -311,6 +312,7 @@ class ExecutorV1 {
311
312
  if (op.op !== "coverage.hit")
312
313
  this.commitStep();
313
314
  try {
315
+ act.instruction = op;
314
316
  this.execute(act, op);
315
317
  }
316
318
  catch (error) {
@@ -325,6 +327,7 @@ class ExecutorV1 {
325
327
  return;
326
328
  this.commitStep();
327
329
  try {
330
+ act.instruction = act.block.term;
328
331
  this.terminate(act, act.block.term);
329
332
  }
330
333
  catch (error) {
@@ -725,8 +728,7 @@ class ExecutorV1 {
725
728
  previous?.budget?.changed();
726
729
  actor.budget?.changed();
727
730
  }
728
- failActorBudget(actor) {
729
- const fault = { kind: "trap", trap: "heap_exceeded" };
731
+ failActorBudget(actor, fault = { kind: "trap", trap: "heap_exceeded" }) {
730
732
  if (!actor.active)
731
733
  this.crashActor(actor, fault);
732
734
  else {
@@ -1944,6 +1946,7 @@ class ExecutorV1 {
1944
1946
  message: renderMessage(message),
1945
1947
  ...(assertion ? { assertion } : {}),
1946
1948
  };
1949
+ this.captureFault(act, act.fault);
1947
1950
  if (act.context.actor)
1948
1951
  act.context.actor.crashing ??= act.fault;
1949
1952
  this.takeEdge(act, term.cleanup);
@@ -1951,6 +1954,7 @@ class ExecutorV1 {
1951
1954
  }
1952
1955
  case "checked_raise":
1953
1956
  act.fault = { kind: "trap", trap: term.trap };
1957
+ this.captureFault(act, act.fault);
1954
1958
  if (act.context.actor)
1955
1959
  act.context.actor.crashing ??= act.fault;
1956
1960
  this.takeEdge(act, term.cleanup);
@@ -2644,6 +2648,7 @@ class ExecutorV1 {
2644
2648
  /// Raise a fault at a call site: enter the named unwind edge, or end the
2645
2649
  /// program when the call names none.
2646
2650
  raiseFault(act, fault, unwind) {
2651
+ this.captureFault(act, fault);
2647
2652
  if (!unwind) {
2648
2653
  this.running = false;
2649
2654
  act.context.failed(fault);
@@ -2653,6 +2658,8 @@ class ExecutorV1 {
2653
2658
  this.takeEdge(act, unwind);
2654
2659
  }
2655
2660
  resumeUnwind(act, handback) {
2661
+ if (act.fault)
2662
+ this.captureFault(act, act.fault);
2656
2663
  if (this.finishConsumedCallable(act, null, () => this.resumeUnwind(act, handback)))
2657
2664
  return;
2658
2665
  if (this.finishConstruction(act, () => this.resumeUnwind(act, handback)))
@@ -2974,9 +2981,9 @@ class ExecutorV1 {
2974
2981
  const wake = this.park(act, term);
2975
2982
  let target;
2976
2983
  let checkWait;
2977
- const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault) => {
2984
+ const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault, _envelope, actorFault) => {
2978
2985
  act.fault ??= closeFault ?? null;
2979
- return wake(this.completion(term.result_shape, term.error_shape, value, error));
2986
+ return wake(this.completion(term.result_shape, term.error_shape, value, error, actorFault, act.accountActor?.budget));
2980
2987
  }, term.request_shapes, (actor) => {
2981
2988
  target = actor;
2982
2989
  checkWait?.();
@@ -3056,6 +3063,7 @@ class ExecutorV1 {
3056
3063
  act.depth > MAX_FRAME_DEPTH &&
3057
3064
  act.block.id === act.fn.entry) {
3058
3065
  act.fault = { kind: "trap", trap: "stack_overflow" };
3066
+ this.captureFault(act, act.fault);
3059
3067
  this.takeEdge(act, term.cancel);
3060
3068
  return;
3061
3069
  }
@@ -3882,8 +3890,10 @@ class ExecutorV1 {
3882
3890
  },
3883
3891
  };
3884
3892
  const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
3885
- if (invoker)
3893
+ if (invoker) {
3886
3894
  frame.depth = invoker.depth + 1;
3895
+ frame.traceCaller = invoker;
3896
+ }
3887
3897
  retainedBy?.managedChildren.add(frame);
3888
3898
  let cancelled = null;
3889
3899
  context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
@@ -3919,7 +3929,161 @@ class ExecutorV1 {
3919
3929
  }
3920
3930
  return value;
3921
3931
  }
3922
- completion(result, errorShape, value, error) {
3932
+ /// Bytecode has real function identities but no file/line table. Capture
3933
+ /// up to the native snapshot's 48 physical activations at the origin, before
3934
+ /// unwind replaces them. The VM always captures available frames; it has no
3935
+ /// environment-variable trace policy. Repeated frames retain their count.
3936
+ captureFault(act, fault) {
3937
+ const origin = fault.primary ?? fault;
3938
+ if (origin.frames !== undefined)
3939
+ return;
3940
+ origin.frames = [];
3941
+ let previous;
3942
+ let previousInstruction;
3943
+ let previousBlock;
3944
+ let previousIndex;
3945
+ for (let frame = act, count = 0; frame && count < 48; frame = frame.caller ?? frame.traceCaller ?? null, count++) {
3946
+ if (!failureTextPrefix(frame.fn.name).complete) {
3947
+ previous = undefined;
3948
+ previousInstruction = undefined;
3949
+ continue;
3950
+ }
3951
+ if (previous === frame.fn.id &&
3952
+ frame.instruction !== undefined &&
3953
+ previousInstruction === frame.instruction &&
3954
+ previousBlock === frame.block.id &&
3955
+ previousIndex === frame.opIndex)
3956
+ origin.frames.at(-1).repeat++;
3957
+ else
3958
+ origin.frames.push({ function: frame.fn.name, repeat: 1 });
3959
+ previous = frame.fn.id;
3960
+ previousInstruction = frame.instruction;
3961
+ previousBlock = frame.block.id;
3962
+ previousIndex = frame.opIndex;
3963
+ }
3964
+ }
3965
+ trappedError(errorShape, fault, budget) {
3966
+ const row = (this.pkg.managed_types ?? []).find((row) => row.kind === "variant" && row.shape === errorShape);
3967
+ if (!row || row.kind !== "variant")
3968
+ throw new Error("actor error lacks its managed variant descriptor");
3969
+ const tag = runtimeTag(this.pkg.variants[errorShape], "ActorErrorTrapped");
3970
+ const type = row.cases[tag]?.[0];
3971
+ if (type == null)
3972
+ throw new Error("trapped actor completion lacks its crash payload descriptor");
3973
+ return this.variant(errorShape, "ActorErrorTrapped", [
3974
+ this.crashInfo(type, fault, budget),
3975
+ ]);
3976
+ }
3977
+ crashInfo(type, fault, budget) {
3978
+ const origin = fault.primary ?? fault;
3979
+ const info = this.managed.row(type);
3980
+ if (info.kind !== "aggregate" || info.fields.length !== 5)
3981
+ throw new Error("crash payload lacks its five-field managed descriptor");
3982
+ const variant = (type) => {
3983
+ const row = this.managed.row(type);
3984
+ if (row.kind !== "variant")
3985
+ throw new Error("crash payload child is not a variant");
3986
+ return row;
3987
+ };
3988
+ const option = (type, value) => this.variant(variant(type).shape, value === undefined ? "OptionNone" : "OptionSome", value === undefined ? [] : [value]);
3989
+ const someType = (type) => {
3990
+ const row = variant(type);
3991
+ const child = row.cases[runtimeTag(this.pkg.variants[row.shape], "OptionSome")]?.[0];
3992
+ if (child == null)
3993
+ throw new Error("crash payload option lacks its child descriptor");
3994
+ return child;
3995
+ };
3996
+ const aggregateShape = (type) => {
3997
+ const row = this.managed.row(type);
3998
+ if (row.kind !== "aggregate")
3999
+ throw new Error("crash payload record lacks its descriptor");
4000
+ const shape = row.shape;
4001
+ if (shape == null)
4002
+ throw new Error("crash payload record lacks its aggregate identity");
4003
+ return shape;
4004
+ };
4005
+ const record = (type, fields) => {
4006
+ const shape = aggregateShape(type);
4007
+ return {
4008
+ kind: "record",
4009
+ typeId: this.aggregateName(shape),
4010
+ shape,
4011
+ fields,
4012
+ };
4013
+ };
4014
+ const [kindType, trapType, , siteType, traceType] = info.fields;
4015
+ const [code, reason] = faultInfo(origin);
4016
+ const kind = this.variant(variant(kindType).shape, code === 200
4017
+ ? "CrashKindHeapExceeded"
4018
+ : code === 214
4019
+ ? "CrashKindTimerOverdue"
4020
+ : code === 213
4021
+ ? "CrashKindHandlerFailed"
4022
+ : code === 215
4023
+ ? "CrashKindStackOverflow"
4024
+ : "CrashKindFault");
4025
+ const trap = variant(someType(trapType));
4026
+ const trapTag = this.pkg.variants[trap.shape].cases.findIndex((item) => item.name === reason);
4027
+ const trapValue = trapTag < 0
4028
+ ? undefined
4029
+ : {
4030
+ kind: "enum",
4031
+ typeId: this.variantName(trap.shape),
4032
+ shape: trap.shape,
4033
+ tag: trapTag,
4034
+ payload: [],
4035
+ };
4036
+ const vector = this.managed.row(someType(traceType));
4037
+ if (vector.kind !== "vector")
4038
+ throw new Error("crash trace lacks its vector descriptor");
4039
+ const frame = this.managed.row(vector.element);
4040
+ if (frame.kind !== "aggregate" || frame.fields.length !== 3)
4041
+ throw new Error("crash frame lacks its managed descriptor");
4042
+ const frames = {
4043
+ kind: "vector",
4044
+ elementType: this.aggregateName(aggregateShape(vector.element)),
4045
+ items: (origin.frames ?? []).map((item) => record(vector.element, [
4046
+ stringValue(item.function),
4047
+ option(frame.fields[1]),
4048
+ { kind: "i64", value: BigInt(item.repeat) },
4049
+ ])),
4050
+ };
4051
+ const value = record(type, [
4052
+ kind,
4053
+ option(trapType, trapValue),
4054
+ stringValue(boundedFailureMessage(faultApplicationMessage(origin) ?? reason)),
4055
+ option(siteType),
4056
+ option(traceType, origin.frames === undefined ? undefined : frames),
4057
+ ]);
4058
+ if (value.kind !== "record")
4059
+ throw new Error("crash payload is not a record");
4060
+ // Diagnostics must not replace the original failure with an allocation
4061
+ // failure. Drop trace storage first, then the message (site is unavailable).
4062
+ for (const omit of [null, 4, 2]) {
4063
+ if (omit === 4)
4064
+ value.fields[4] = option(traceType);
4065
+ if (omit === 2)
4066
+ value.fields[2] = stringValue("");
4067
+ try {
4068
+ this.managed.stampGraph({ value, type });
4069
+ budget?.check([{ value, type }]);
4070
+ return value;
4071
+ }
4072
+ catch (error) {
4073
+ if (!(error instanceof HeapExceeded))
4074
+ throw error;
4075
+ }
4076
+ }
4077
+ return value;
4078
+ }
4079
+ completion(result, errorShape, value, error, fault, budget) {
4080
+ if (error === "Trapped") {
4081
+ if (!fault)
4082
+ throw new Error("trapped actor completion lost its originating fault");
4083
+ return this.variant(result, "ResultErr", [
4084
+ this.trappedError(errorShape, fault, budget),
4085
+ ]);
4086
+ }
3923
4087
  return error
3924
4088
  ? this.variant(result, "ResultErr", [
3925
4089
  this.variant(errorShape, actorErrorRole(error), (error === "Failed" || error === "Rejected") && value
@@ -4261,7 +4425,8 @@ class ExecutorV1 {
4261
4425
  const task = this.newTask(act.context.actor?.budget, term.result && term.result !== "never"
4262
4426
  ? (term.result.managed_type ?? undefined)
4263
4427
  : undefined);
4264
- const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope) => {
4428
+ const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope, actorFault) => {
4429
+ task.actorFault = actorFault;
4265
4430
  const reply = this.managedCompletion(task, value, error, envelope);
4266
4431
  if (reply && !closeFault && act.context.actor?.budget) {
4267
4432
  try {
@@ -4270,12 +4435,12 @@ class ExecutorV1 {
4270
4435
  catch (failure) {
4271
4436
  if (!(failure instanceof HeapExceeded))
4272
4437
  throw failure;
4438
+ const fault = { kind: "trap", trap: "heap_exceeded" };
4439
+ this.captureFault(act, fault);
4273
4440
  task.actorError = "Trapped";
4274
- this.settleTask(task, UNIT, {
4275
- kind: "trap",
4276
- trap: "heap_exceeded",
4277
- });
4278
- this.failActorBudget(act.context.actor);
4441
+ task.actorFault = fault;
4442
+ this.settleTask(task, UNIT, fault);
4443
+ this.failActorBudget(act.context.actor, fault);
4279
4444
  return false;
4280
4445
  }
4281
4446
  }
@@ -4308,7 +4473,7 @@ class ExecutorV1 {
4308
4473
  task.value = UNIT;
4309
4474
  task.taken = true;
4310
4475
  const result = task.managedResult ??
4311
- this.completion(term.result_shape, term.error_shape, value, task.actorError);
4476
+ this.completion(term.result_shape, term.error_shape, value, task.actorError, task.actorFault, act.accountActor?.budget);
4312
4477
  task.managedResult = undefined;
4313
4478
  task.managedReply = undefined;
4314
4479
  this.completeShim(act, term, result);
@@ -4372,7 +4537,7 @@ class ExecutorV1 {
4372
4537
  this.closeValueAsync(payload, "actor heap limit exceeded", (fault) => {
4373
4538
  act.fault ??= fault;
4374
4539
  finish(this.variant(term.result_shape, "ResultErr", [
4375
- this.variant(term.error_shape, "ActorErrorTrapped"),
4540
+ this.trappedError(term.error_shape, actor.crashing ?? { kind: "trap", trap: "heap_exceeded" }, act.accountActor?.budget),
4376
4541
  ]));
4377
4542
  }, act.context.actor, act.rcOwner);
4378
4543
  return;
@@ -4692,12 +4857,12 @@ class ExecutorV1 {
4692
4857
  ? { kind: "Actor", identity: owner.identity }
4693
4858
  : { kind: "Task" };
4694
4859
  let receiver = null;
4695
- const finish = (value, error, drained, envelope) => {
4860
+ const finish = (value, error, drained, envelope, actorFault) => {
4696
4861
  const alreadySettled = settled;
4697
4862
  settled = true;
4698
4863
  cancelTimer?.();
4699
4864
  if (alreadySettled ||
4700
- complete(value, error, undefined, envelope) === false) {
4865
+ complete(value, error, undefined, envelope, actorFault) === false) {
4701
4866
  // The finishing receiver still owns a reply that its caller no
4702
4867
  // longer accepts. Its next turn must wait for this release.
4703
4868
  this.closeValueAsync(value ?? UNIT, null, (fault) => {
@@ -4733,7 +4898,7 @@ class ExecutorV1 {
4733
4898
  waiter(fault);
4734
4899
  }, owner, rcOwner);
4735
4900
  };
4736
- const reject = (reason) => {
4901
+ const reject = (reason, actorFault) => {
4737
4902
  if (settled || closing)
4738
4903
  return;
4739
4904
  closeRequest((fault) => {
@@ -4741,7 +4906,7 @@ class ExecutorV1 {
4741
4906
  return;
4742
4907
  settled = true;
4743
4908
  cancelTimer?.();
4744
- complete(null, reason, fault);
4909
+ complete(null, reason, fault, undefined, actorFault);
4745
4910
  });
4746
4911
  };
4747
4912
  const attempt = () => {
@@ -4812,7 +4977,7 @@ class ExecutorV1 {
4812
4977
  if (!(error instanceof HeapExceeded))
4813
4978
  throw error;
4814
4979
  this.failActorBudget(actor);
4815
- reject("Trapped");
4980
+ reject("Trapped", actor.crashing ?? { kind: "trap", trap: "heap_exceeded" });
4816
4981
  return;
4817
4982
  }
4818
4983
  admitted = true;
@@ -5141,7 +5306,7 @@ class ExecutorV1 {
5141
5306
  this.closeValueAsync(value, this.faultText(fault), (closeFault) => {
5142
5307
  released();
5143
5308
  this.crashActor(actor, combineFaults(fault, closeFault), message.handler);
5144
- message.complete(null, "Trapped");
5309
+ message.complete(null, "Trapped", undefined, undefined, fault);
5145
5310
  }, actor, actor.rcOwner);
5146
5311
  return;
5147
5312
  }
@@ -5163,11 +5328,13 @@ class ExecutorV1 {
5163
5328
  if (message.reply)
5164
5329
  message.complete(value.payload[0] ?? UNIT, "Failed", drained, value);
5165
5330
  else {
5166
- this.crashActor(actor, {
5331
+ const fault = {
5167
5332
  kind: "panic",
5168
5333
  handlerFailed: true,
5169
5334
  message: "unhandled declared handler failure",
5170
- }, message.handler);
5335
+ };
5336
+ this.captureFault(frame, fault);
5337
+ this.crashActor(actor, fault, message.handler);
5171
5338
  return;
5172
5339
  }
5173
5340
  }
@@ -5196,7 +5363,7 @@ class ExecutorV1 {
5196
5363
  }
5197
5364
  else {
5198
5365
  this.crashActor(actor, fault, message.handler);
5199
- message.complete(null, "Trapped");
5366
+ message.complete(null, "Trapped", undefined, undefined, fault);
5200
5367
  }
5201
5368
  },
5202
5369
  });
@@ -5276,31 +5443,11 @@ class ExecutorV1 {
5276
5443
  };
5277
5444
  const runHook = () => {
5278
5445
  if (actor.layout.crash != null) {
5279
- const [code, reason] = faultInfo(fault);
5280
- const message = faultApplicationMessage(fault) ?? reason;
5281
- const info = {
5282
- kind: "record",
5283
- typeId: this.aggregateName(actor.layout.crash_info),
5284
- shape: actor.layout.crash_info,
5285
- fields: [
5286
- {
5287
- kind: "enum",
5288
- typeId: this.variantName(actor.layout.crash_kind),
5289
- shape: actor.layout.crash_kind,
5290
- tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 200
5291
- ? "CrashKindHeapExceeded"
5292
- : code === 214
5293
- ? "CrashKindTimerOverdue"
5294
- : code === 213
5295
- ? "CrashKindHandlerFailed"
5296
- : code === 215
5297
- ? "CrashKindStackOverflow"
5298
- : "CrashKindFault"),
5299
- payload: [],
5300
- },
5301
- stringValue(String(message)),
5302
- ],
5303
- };
5446
+ const type = this.functionAt(actor.layout.crash).params[1]
5447
+ ?.managed_type;
5448
+ if (type == null)
5449
+ throw new Error("crash hook lacks its crash payload descriptor");
5450
+ const info = this.crashInfo(type, fault, actor.budget);
5304
5451
  let cancelTimer;
5305
5452
  const complete = (action) => {
5306
5453
  cancelTimer?.();
@@ -5850,6 +5997,7 @@ class ExecutorV1 {
5850
5997
  });
5851
5998
  }
5852
5999
  haltWithFault(fault) {
6000
+ this.captureFault(this.current, fault);
5853
6001
  const isPanic = fault.kind === "panic";
5854
6002
  const status = isPanic ? "panic" : "trap";
5855
6003
  const trapKind = isPanic ? "panic" : fault.trap;
@@ -5866,6 +6014,25 @@ class ExecutorV1 {
5866
6014
  throw new Halt(status);
5867
6015
  }
5868
6016
  }
6017
+ /// Retain only bounded scratch space even when raw fault text or a compiler
6018
+ /// function name is much larger than the public diagnostic limit.
6019
+ function failureTextPrefix(text) {
6020
+ const bytes = new Uint8Array(FAILURE_TEXT_LIMIT);
6021
+ const { read, written } = new TextEncoder().encodeInto(text, bytes);
6022
+ return { bytes: bytes.subarray(0, written), complete: read === text.length };
6023
+ }
6024
+ /// Match the owned native snapshot without changing raw fault reporting.
6025
+ /// The suffix counts inside the UTF-8 limit and never splits a character.
6026
+ function boundedFailureMessage(message) {
6027
+ const { bytes, complete } = failureTextPrefix(message);
6028
+ if (complete)
6029
+ return message;
6030
+ let end = Math.min(bytes.length, FAILURE_TEXT_LIMIT -
6031
+ new TextEncoder().encode(FAILURE_TRUNCATED_SUFFIX).length);
6032
+ while ((bytes[end] & 0xc0) === 0x80)
6033
+ end--;
6034
+ return (new TextDecoder().decode(bytes.subarray(0, end)) + FAILURE_TRUNCATED_SUFFIX);
6035
+ }
5869
6036
  function combineFaults(primary, secondary) {
5870
6037
  if (!primary)
5871
6038
  return secondary;
@@ -0,0 +1,2 @@
1
+ export declare const FAILURE_TEXT_LIMIT = 4096;
2
+ export declare const FAILURE_TRUNCATED_SUFFIX = " [truncated]";
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/gen-failure-kinds.py; do not edit.
2
+ export const FAILURE_TEXT_LIMIT = 4096;
3
+ export const FAILURE_TRUNCATED_SUFFIX = " [truncated]";
@@ -308,6 +308,10 @@ export type ManagedType = {
308
308
  element: number;
309
309
  } | {
310
310
  kind: "aggregate";
311
+ /**
312
+ * The canonical record shape when this layout represents a record.
313
+ */
314
+ shape?: number | null;
311
315
  fields: Array<number>;
312
316
  } | {
313
317
  kind: "variant";
@@ -749,7 +753,7 @@ export type RuntimeCallFamily = {
749
753
  IntArith: [IntArithKind, IntMethodWidth];
750
754
  } | {
751
755
  FloatMethod: FloatMethodOp;
752
- } | "NodeConnect" | "NodeId" | "NodeIdDisplay" | "LocationNodeId" | "LocationIncarnation" | "LocationDisplay" | "NodeIdentityKey" | "NodeLoadKeys" | "NodeLookup" | "NodeRegister" | "NodeSetTransport" | "NodeShutdown" | "NodeStart" | "MetricCounterRegister" | "MetricCounterInc" | "MetricCounterAdd" | "MetricGaugeRegister" | "MetricGaugeSet" | "MetricGaugeInc" | "MetricGaugeDec" | "MetricGaugeAdd" | "MetricHistogramRegister" | "MetricHistogramRegisterSimple" | "MetricHistogramRecord" | "MetricVecRegister" | "MetricVecWith" | "ObserveReadU64" | "ObserveScrape" | "ObserveSeries" | "ObserveBarrier" | "RcClone" | "RcDowngrade" | "RcDrop" | "RcGet" | "RcIsUnique" | "RcNew" | "RcSet" | "RcStrongCount" | "RcWeakCount" | "WeakCloneRc" | "WeakDropRc" | "WeakUpgradeRc" | "RegexCapture" | "RegexCompile" | "RegexFreeCapture" | "RegexHandle" | "RegexMatch" | "ReplyChannelCancel" | "ReplyChannelFree" | "ReplyChannelNew" | "ReplyPayloadFree" | "ReplyWait" | "SelectFirst" | "SinkClone" | "SinkClose" | "StreamClose" | "StreamForward" | "StreamPipe" | "StreamFromGenerator" | "SinkFromEncoder" | "StreamNextLayout" | "StreamPairSink" | "StreamPairStream" | "StreamSendLayout" | "StreamTake" | "StreamTryNextLayout" | "StreamTrySendLayout" | "CharIsDigit" | "CharIsLetter" | "CharIsAlphanumeric" | "CharIsWhitespace" | "CharIsUpper" | "CharIsLower" | "CharIsPunctuation" | "CharIsControl" | "CharIsAscii" | "CharToUpper" | "CharToLower" | "CharToTitle" | "CharLenUtf8" | "CharToDigit" | "CharFromI64" | "CharFromU64" | "StringGet" | "StringGetRange" | "StringSliceRange" | "StringAppend" | "StringPush" | "StringClear" | "StringFindFrom" | "StringByteLen" | "StringConcat" | "StringEquals" | "StringCompare" | "StringStartsWith" | "StringEndsWith" | "StringContains" | "StringIsEmpty" | "StructuralFormat" | "StringFind" | "StringIndex" | "StringLen" | "StringRepeat" | "StringReplace" | "StringClone" | "StringSplit" | "StringLines" | "StringSliceCodepoints" | "StringSliceCodepointsFrom" | "StringToLowercase" | "StringToBytes" | "StringToUppercase" | "StringTrim" | "U8ToString" | "I32ToString" | "I64ToString" | "U32ToString" | "U64ToString" | "F64ToString" | "CharToString" | {
756
+ } | "NodeConnect" | "NodeId" | "NodeIdDisplay" | "LocationNodeId" | "LocationIncarnation" | "LocationDisplay" | "NodeIdentityKey" | "NodeLoadKeys" | "NodeLookup" | "NodeRegister" | "NodeSetTransport" | "NodeShutdown" | "NodeStart" | "MetricCounterRegister" | "MetricCounterInc" | "MetricCounterAdd" | "MetricGaugeRegister" | "MetricGaugeSet" | "MetricGaugeInc" | "MetricGaugeDec" | "MetricGaugeAdd" | "MetricHistogramRegister" | "MetricHistogramRegisterSimple" | "MetricHistogramRecord" | "MetricVecRegister" | "MetricVecWith" | "ObserveReadU64" | "ObserveScrape" | "ObserveSeries" | "ObserveBarrier" | "RcClone" | "RcDowngrade" | "RcDrop" | "RcGet" | "RcIsUnique" | "RcNew" | "RcSet" | "RcStrongCount" | "RcWeakCount" | "WeakCloneRc" | "WeakDropRc" | "WeakUpgradeRc" | "RegexCapture" | "RegexCompile" | "RegexFreeCapture" | "RegexHandle" | "RegexMatch" | "ReplyChannelCancel" | "ReplyChannelFree" | "ReplyChannelNew" | "ReplyPayloadFree" | "ReplyWait" | "SelectFirst" | "SinkClone" | "SinkClose" | "StreamClose" | "StreamForward" | "StreamPipe" | "StreamFromGenerator" | "SinkFromEncoder" | "StreamNextLayout" | "StreamPairSink" | "StreamPairStream" | "StreamSendLayout" | "StreamTake" | "StreamTryNextLayout" | "StreamTrySendLayout" | "CharIsDigit" | "CharIsLetter" | "CharIsAlphanumeric" | "CharIsWhitespace" | "CharIsUpper" | "CharIsLower" | "CharIsPunctuation" | "CharIsControl" | "CharIsAscii" | "CharToUpper" | "CharToLower" | "CharToTitle" | "CharLenUtf8" | "CharToDigit" | "CharFromI64" | "CharFromU64" | "StringGet" | "StringGetRange" | "StringSliceRange" | "StringAppend" | "StringPush" | "StringClear" | "StringAppendI64" | "StringAppendU64" | "StringAppendF64" | "StringAppendBool" | "StringAppendLiteral" | "StringFindFrom" | "StringByteLen" | "StringConcat" | "StringEquals" | "StringCompare" | "StringStartsWith" | "StringEndsWith" | "StringContains" | "StringIsEmpty" | "StructuralFormat" | "StringFind" | "StringIndex" | "StringLen" | "StringRepeat" | "StringReplace" | "StringClone" | "StringSplit" | "StringLines" | "StringSliceCodepoints" | "StringSliceCodepointsFrom" | "StringToLowercase" | "StringToBytes" | "StringToUppercase" | "StringTrim" | "U8ToString" | "I32ToString" | "I64ToString" | "U32ToString" | "U64ToString" | "F64ToString" | "CharToString" | {
753
757
  Print: {
754
758
  kind: PrintKind;
755
759
  newline: boolean;
@@ -54,6 +54,12 @@ export function validatePackageGraph(pkg) {
54
54
  managedType(row.value);
55
55
  break;
56
56
  case "aggregate":
57
+ if (row.shape != null) {
58
+ const shape = at(pkg.aggregates, row.shape, "managed aggregate shape");
59
+ require(shape.fields.length ===
60
+ row.fields
61
+ .length, "managed aggregate fields differ from its shape");
62
+ }
57
63
  row.fields.forEach(managedType);
58
64
  break;
59
65
  case "variant":