ihsm 0.1.1 → 0.1.21

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 (43) hide show
  1. package/lib/cjs/index.d.ts +4 -1
  2. package/lib/cjs/index.js +13 -1
  3. package/lib/cjs/index.js.map +1 -1
  4. package/lib/cjs/internal/console-instrumentation.d.ts +34 -0
  5. package/lib/cjs/internal/console-instrumentation.js +71 -0
  6. package/lib/cjs/internal/console-instrumentation.js.map +1 -0
  7. package/lib/cjs/internal/identity.d.ts +16 -0
  8. package/lib/cjs/internal/identity.js +170 -0
  9. package/lib/cjs/internal/identity.js.map +1 -0
  10. package/lib/cjs/internal/instrumentation.d.ts +47 -0
  11. package/lib/cjs/internal/instrumentation.js +201 -0
  12. package/lib/cjs/internal/instrumentation.js.map +1 -0
  13. package/lib/cjs/internal/runtime.d.ts +89 -6
  14. package/lib/cjs/internal/runtime.js +676 -52
  15. package/lib/cjs/internal/runtime.js.map +1 -1
  16. package/lib/cjs/internal/types.d.ts +174 -7
  17. package/lib/cjs/internal/types.js.map +1 -1
  18. package/lib/cjs/testing.d.ts +86 -1
  19. package/lib/cjs/testing.js +54 -0
  20. package/lib/cjs/testing.js.map +1 -1
  21. package/lib/cjs/types.d.ts +1 -1
  22. package/lib/esm/index.d.ts +4 -1
  23. package/lib/esm/index.js +3 -1
  24. package/lib/esm/index.js.map +1 -1
  25. package/lib/esm/internal/console-instrumentation.d.ts +34 -0
  26. package/lib/esm/internal/console-instrumentation.js +68 -0
  27. package/lib/esm/internal/console-instrumentation.js.map +1 -0
  28. package/lib/esm/internal/identity.d.ts +16 -0
  29. package/lib/esm/internal/identity.js +159 -0
  30. package/lib/esm/internal/identity.js.map +1 -0
  31. package/lib/esm/internal/instrumentation.d.ts +47 -0
  32. package/lib/esm/internal/instrumentation.js +178 -0
  33. package/lib/esm/internal/instrumentation.js.map +1 -0
  34. package/lib/esm/internal/runtime.d.ts +89 -6
  35. package/lib/esm/internal/runtime.js +666 -51
  36. package/lib/esm/internal/runtime.js.map +1 -1
  37. package/lib/esm/internal/types.d.ts +174 -7
  38. package/lib/esm/internal/types.js.map +1 -1
  39. package/lib/esm/testing.d.ts +86 -1
  40. package/lib/esm/testing.js +53 -0
  41. package/lib/esm/testing.js.map +1 -1
  42. package/lib/esm/types.d.ts +1 -1
  43. package/package.json +1 -1
@@ -1,4 +1,6 @@
1
1
  import { kHandlerMachine, kParentLink } from './types.js';
2
+ import { actorNameFromTopState, childActorPath, mintActorIdentity, rootActorPath } from './identity.js';
3
+ import { getActiveInstrumentation, getTaskMeta, notifyActorCreated, notifyActorSpawned, notifyEnqueue, notifyError, notifyLog, notifyMacrostepBegin, notifyMacrostepEnd, notifyMicrostepBegin, notifyMicrostepEnd, notifyOutboundCallBegin, notifyOutboundCallEnd, notifyPortCallBegin, notifyPortCallEnd, setTaskMeta } from './instrumentation.js';
2
4
  //#region TraceLevel
3
5
  export var TraceLevel;
4
6
  (function (TraceLevel) {
@@ -220,6 +222,20 @@ export class FatalErrorState extends TopState {
220
222
  defineStateName(TopState, 'TopState');
221
223
  defineStateName(FatalErrorState, 'FatalErrorState');
222
224
  /** @internal */
225
+ export function lookupHandlerState(hsm, eventName) {
226
+ let state = hsm.currentState;
227
+ while (true) {
228
+ const prototype = state.prototype;
229
+ if (Object.prototype.hasOwnProperty.call(prototype, eventName)) {
230
+ return getStateName(state);
231
+ }
232
+ if (state === TopState) {
233
+ return undefined;
234
+ }
235
+ state = Object.getPrototypeOf(state);
236
+ }
237
+ }
238
+ /** @internal */
223
239
  export function lookupEventHandler(hsm, eventName) {
224
240
  let state = hsm.currentState;
225
241
  while (true) {
@@ -444,19 +460,29 @@ export function splitServiceArgs(args) {
444
460
  }
445
461
  return { callArgs: args.slice(0, -1), timeoutMs };
446
462
  }
447
- export function serviceCallWithTimeout(promise, method, timeoutMs) {
463
+ /**
464
+ * Race a service-call promise against a `timeoutMs` deadline.
465
+ *
466
+ * The deadline is armed through `timer` — the actor's port timer service — so that under a
467
+ * {@link TestPort} virtual clock a call timeout is driven by `port.advance(...)` and stays
468
+ * fully deterministic. When no port timer is available the host timer is used (production
469
+ * `Port` already delegates to the host timer, so behaviour there is unchanged).
470
+ */
471
+ export function serviceCallWithTimeout(promise, method, timeoutMs, timer) {
448
472
  if (timeoutMs === 0) {
449
473
  return Promise.reject(new CallTimeoutError(method));
450
474
  }
475
+ const arm = (callback) => (timer !== undefined ? timer.setTimeout(callback, timeoutMs) : globalThis.setTimeout(callback, timeoutMs));
476
+ const disarm = (handle) => (timer !== undefined ? timer.clearTimeout(handle) : globalThis.clearTimeout(handle));
451
477
  return new Promise((resolve, reject) => {
452
- const timer = setTimeout(() => {
478
+ const handle = arm(() => {
453
479
  reject(new CallTimeoutError(method));
454
- }, timeoutMs);
480
+ });
455
481
  promise.then(value => {
456
- clearTimeout(timer);
482
+ disarm(handle);
457
483
  resolve(value);
458
484
  }, err => {
459
- clearTimeout(timer);
485
+ disarm(handle);
460
486
  reject(err);
461
487
  });
462
488
  });
@@ -488,8 +514,17 @@ function getFacetProto(topState, index, kind, facet) {
488
514
  if (facet === 'call') {
489
515
  built[name] = function (...args) {
490
516
  const { callArgs, timeoutMs } = splitServiceArgs(args);
491
- const promise = this[kMachine].dispatchService(name, callArgs);
492
- return timeoutMs === undefined ? promise : serviceCallWithTimeout(promise, name, timeoutMs);
517
+ const machine = this[kMachine];
518
+ const begin = machine.beginOutboundCall?.(name, machine.actorUuid);
519
+ const promise = machine.dispatchService(name, callArgs).then(value => {
520
+ machine.endOutboundCall?.(begin, 'ok');
521
+ return value;
522
+ }, cause => {
523
+ const err = asError(cause);
524
+ machine.endOutboundCall?.(begin, 'error', err);
525
+ throw err;
526
+ });
527
+ return timeoutMs === undefined ? promise : serviceCallWithTimeout(promise, name, timeoutMs, machine.callTimer);
493
528
  };
494
529
  }
495
530
  else {
@@ -526,6 +561,12 @@ export function createActorHandle(machine, topState, index, kind) {
526
561
  Object.defineProperty(handle, 'notify', { value: createFacet(machine, topState, index, kind, 'notify'), enumerable: true });
527
562
  Object.defineProperty(handle, 'notifyNow', { value: createFacet(machine, topState, index, kind, 'notifyNow'), enumerable: true });
528
563
  Object.defineProperty(handle, 'call', { value: createFacet(machine, topState, index, kind, 'call'), enumerable: true });
564
+ Object.defineProperty(handle, 'id', {
565
+ enumerable: true,
566
+ get() {
567
+ return machine.actorUuid;
568
+ },
569
+ });
529
570
  handle.hsm = machine.actorHsmFor(kind);
530
571
  return handle;
531
572
  }
@@ -567,6 +608,18 @@ export class SelfCallDeadlockError extends Error {
567
608
  this.name = 'SelfCallDeadlockError';
568
609
  }
569
610
  }
611
+ function errorPhaseFromError(err) {
612
+ if (err instanceof TransitionError) {
613
+ return err.failedCallback === 'onEntry' ? 'onEntry' : 'onExit';
614
+ }
615
+ if (err instanceof UnhandledEventError)
616
+ return 'unhandled';
617
+ if (err instanceof InitializationError)
618
+ return 'initialize';
619
+ if (err instanceof EventHandlerError)
620
+ return 'handler';
621
+ return 'handler';
622
+ }
570
623
  /**
571
624
  * Lazy Node AsyncLocalStorage for non-production deadlock detection.
572
625
  * State lives in a closure — no exported mutable slot.
@@ -601,6 +654,45 @@ export const dispatchContext = (() => {
601
654
  }
602
655
  return { get, resetInit, markUnavailable };
603
656
  })();
657
+ /**
658
+ * Best-effort current runtime trace anchor (`actorUuid`, `macrostepId`, `stepSeq`) for user code.
659
+ * Returns `undefined` when called outside an active handler dispatch turn.
660
+ */
661
+ export function currentTraceAnchor() {
662
+ const token = dispatchContext.get()?.getStore();
663
+ if (token?.actorUuid === undefined)
664
+ return undefined;
665
+ return {
666
+ actorUuid: token.actorUuid,
667
+ macrostepId: token.macrostepId,
668
+ stepSeq: token.stepSeq,
669
+ };
670
+ }
671
+ /** Lazy ALS carrying the currently-executing proxied port call (when instrumentation is active). */
672
+ const portCallContext = (() => {
673
+ let storage = undefined;
674
+ function get() {
675
+ if (storage !== undefined) {
676
+ return storage ?? undefined;
677
+ }
678
+ if (typeof process === 'undefined' || process.versions?.node === undefined) {
679
+ storage = null;
680
+ return undefined;
681
+ }
682
+ try {
683
+ // Dynamic require — keeps browser bundles free of `node:async_hooks`.
684
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
685
+ const hooks = require('node:async_hooks');
686
+ storage = new hooks.AsyncLocalStorage();
687
+ return storage;
688
+ }
689
+ catch {
690
+ storage = null;
691
+ return undefined;
692
+ }
693
+ }
694
+ return { get };
695
+ })();
604
696
  //#region transition-routines
605
697
  /** Thrown when a generated transition table's graph hash does not match the scanned hierarchy. */
606
698
  export class TransitionTableError extends Error {
@@ -651,10 +743,14 @@ export function planTransitionClasses(srcState, destState) {
651
743
  }
652
744
  return { exit: srcPath, entry: dstPath, finalState };
653
745
  }
654
- async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer) {
746
+ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer, hookEvents) {
655
747
  const statePrototype = state.prototype;
656
748
  const stateName = getStateName(state);
657
749
  const hasHook = Object.prototype.hasOwnProperty.call(statePrototype, hook);
750
+ // Emit hook tracer callbacks for real (own) hooks at verbose, or always for a structural seam
751
+ // (instrumentation) whose entry/exit spans are not TraceLevel-gated (spec §4.8). Skipped default
752
+ // hooks are never eventized.
753
+ const emitHookEvents = (style === 'verbose' || hookEvents) && hasHook;
658
754
  if ((style === 'verbose' || style === 'debug') && !hasHook) {
659
755
  if (style === 'verbose') {
660
756
  tracer?.traceHookSkipped(stateName, hook);
@@ -662,16 +758,19 @@ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, to
662
758
  return;
663
759
  }
664
760
  try {
761
+ if (emitHookEvents) {
762
+ tracer?.traceHookStart?.(stateName, hook);
763
+ }
665
764
  const res = statePrototype[hook].call(instance);
666
765
  if (res) {
667
766
  await res;
668
767
  }
669
- if (style === 'verbose') {
768
+ if (emitHookEvents) {
670
769
  tracer?.traceHookDone(stateName, hook);
671
770
  }
672
771
  }
673
772
  catch (cause) {
674
- if (style === 'verbose') {
773
+ if (emitHookEvents) {
675
774
  tracer?.traceHookError(stateName, hook, cause);
676
775
  }
677
776
  throw new TransitionError(hsm, asError(cause), stateName, hook, fromStateName, toStateName);
@@ -685,16 +784,27 @@ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, to
685
784
  export async function executeTransitionRoutine(hsm, instance, plan, srcState, dstState, options = {}) {
686
785
  const style = options.style ?? 'production';
687
786
  const tracer = options.tracer;
787
+ const hookEvents = options.hookEvents ?? false;
688
788
  const fromStateName = getStateName(srcState);
689
789
  const toStateName = getStateName(dstState);
690
- if (style === 'verbose' || style === 'debug') {
691
- tracer?.traceTransitionStart(fromStateName, toStateName);
692
- }
790
+ // The structural transition span fires whenever a tracer is attached (the instrumentation seam is
791
+ // not TraceLevel-gated); the console tracer is only ever supplied off-PRODUCTION, so this is a
792
+ // no-op there unless an explicit instrumentation tracer is present.
793
+ tracer?.traceTransitionStart(fromStateName, toStateName);
693
794
  for (const state of plan.exit) {
694
- await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer);
795
+ await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer, hookEvents);
695
796
  }
797
+ let initializeOpened = false;
696
798
  for (const state of plan.entry) {
697
- await invokeLifecycleHook(hsm, instance, state, 'onEntry', fromStateName, toStateName, style, tracer);
799
+ if (!initializeOpened && state !== dstState) {
800
+ tracer?.traceInitializeStart?.(toStateName);
801
+ initializeOpened = true;
802
+ }
803
+ await invokeLifecycleHook(hsm, instance, state, 'onEntry', fromStateName, toStateName, style, tracer, hookEvents);
804
+ }
805
+ if (initializeOpened) {
806
+ const finalName = plan.finalState !== undefined ? getStateName(plan.finalState) : toStateName;
807
+ tracer?.traceInitializeDone?.(finalName);
698
808
  }
699
809
  const applyState = (next) => {
700
810
  if (options.setCurrentState) {
@@ -717,6 +827,8 @@ export async function executeTransitionRoutine(hsm, instance, plan, srcState, ds
717
827
  }
718
828
  if (plan.finalState) {
719
829
  applyState(plan.finalState);
830
+ // Close the structural transition span on the PRODUCTION path (verbose/debug returned above).
831
+ tracer?.traceTransitionDone(getStateName(plan.finalState));
720
832
  }
721
833
  }
722
834
  export function createTransitionTracer(hsm) {
@@ -724,6 +836,12 @@ export function createTransitionTracer(hsm) {
724
836
  traceTransitionStart(fromStateName, toStateName) {
725
837
  hsm._tracePush(`transition from ${fromStateName} to ${toStateName}`, `started transition from ${fromStateName} to ${toStateName} `);
726
838
  },
839
+ traceInitializeStart(stateName) {
840
+ hsm._tracePush(`initialize ${stateName}`, `started initialize drill-down from ${stateName}`);
841
+ },
842
+ traceInitializeDone(finalStateName) {
843
+ hsm._tracePopDone(`done initialize drill-down at ${finalStateName}`);
844
+ },
727
845
  traceHookDone(stateName, hook) {
728
846
  hsm._traceWrite(`${stateName}.${hook}() done`);
729
847
  },
@@ -755,9 +873,15 @@ class RuntimeTransitionRoutine {
755
873
  }
756
874
  async execute(hsm, srcState, dstState) {
757
875
  const style = hsm.traceLevel === TraceLevel.PRODUCTION ? 'production' : hsm.traceLevel === TraceLevel.DEBUG ? 'debug' : 'verbose';
876
+ const machine = hsm;
877
+ const instTracer = machine.instrumentation?.transition;
878
+ const tracer = instTracer ?? (style !== 'production' ? createTransitionTracer(hsm) : undefined);
758
879
  await executeTransitionRoutine(hsm, hsm._instance, this.plan, srcState, dstState, {
759
880
  style,
760
- ...(style !== 'production' ? { tracer: createTransitionTracer(hsm) } : {}),
881
+ ...(tracer !== undefined ? { tracer } : {}),
882
+ // The instrumentation seam's entry/exit spans are structural (not TraceLevel-gated); the
883
+ // console tracer keeps its verbose-only gating.
884
+ hookEvents: instTracer !== undefined,
761
885
  setCurrentState: state => {
762
886
  hsm.currentState = state;
763
887
  },
@@ -1350,42 +1474,60 @@ export function createInitTask(host, resolver) {
1350
1474
  .then(() => executePendingTransition(host, resolver))
1351
1475
  .then(() => done())
1352
1476
  .catch((err) => {
1353
- host.dispatchErrorCallback(host, asError(err));
1477
+ host.reportDispatchError(asError(err));
1354
1478
  done();
1355
1479
  });
1356
1480
  };
1357
1481
  }
1482
+ /**
1483
+ * Run a dispatch body inside the ambient `dispatchContext` token so that any `notify`/`call`/timer
1484
+ * the handler issues can be attributed to the running `(macrostepId, stepSeq)` (CORE-B, §5.6.3).
1485
+ * Falls back to a plain run when no ALS is available (browser) or the host predates the seam.
1486
+ */
1487
+ function runWithinDispatch(host, run) {
1488
+ const machine = host;
1489
+ const wants = machine.needsDispatchContext?.() ?? host.traceLevel !== TraceLevel.PRODUCTION;
1490
+ if (!wants) {
1491
+ run();
1492
+ return;
1493
+ }
1494
+ const storage = dispatchContext.get();
1495
+ if (storage === undefined) {
1496
+ run();
1497
+ return;
1498
+ }
1499
+ const token = machine.buildDispatchToken?.() ?? { machine: host };
1500
+ storage.run(token, run);
1501
+ }
1502
+ function currentPortCallToken() {
1503
+ const storage = portCallContext.get();
1504
+ return storage?.getStore();
1505
+ }
1358
1506
  /** @internal */
1359
1507
  export function createNotificationTask(host, resolver, name, args) {
1360
1508
  const strategy = dispatchStrategyFor(host.traceLevel);
1361
1509
  return (done) => {
1362
- strategy
1363
- .dispatchEvent(host, resolver, name, ...args)
1364
- .catch((err) => host.dispatchErrorCallback(host, asError(err)))
1365
- .finally(() => done());
1510
+ runWithinDispatch(host, () => {
1511
+ void strategy
1512
+ .dispatchEvent(host, resolver, name, ...args)
1513
+ .catch((err) => host.reportDispatchError(asError(err)))
1514
+ .finally(() => done());
1515
+ });
1366
1516
  };
1367
1517
  }
1368
1518
  /** @internal */
1369
1519
  export function createServiceTask(host, resolver, name, args, resolve, reject) {
1370
- const machine = host;
1371
1520
  return (done) => {
1372
1521
  const run = () => invokeHandler(host, resolver, name, args)
1373
1522
  .then(resolve)
1374
1523
  .catch((err) => {
1375
1524
  reject(asError(err));
1376
1525
  })
1377
- .catch((err) => host.dispatchErrorCallback(host, asError(err)))
1526
+ .catch((err) => host.reportDispatchError(asError(err)))
1378
1527
  .finally(() => done());
1379
- if (host.traceLevel === TraceLevel.PRODUCTION) {
1528
+ runWithinDispatch(host, () => {
1380
1529
  void run();
1381
- return;
1382
- }
1383
- const storage = dispatchContext.get();
1384
- if (storage === undefined) {
1385
- void run();
1386
- return;
1387
- }
1388
- void storage.run({ machine }, run);
1530
+ });
1389
1531
  };
1390
1532
  }
1391
1533
  //#endregion
@@ -1396,6 +1538,9 @@ export class HsmObject {
1396
1538
  topStateName;
1397
1539
  ctxTypeName;
1398
1540
  traceWriter;
1541
+ actorUuid;
1542
+ actorName;
1543
+ actorPath;
1399
1544
  /** @internal */
1400
1545
  _instance;
1401
1546
  /** @internal */
@@ -1410,7 +1555,9 @@ export class HsmObject {
1410
1555
  dispatchErrorCallback;
1411
1556
  _traceLevel;
1412
1557
  _traceDomainStack;
1413
- constructor(TopState, instance, traceWriter, traceLevel, dispatchErrorCallback) {
1558
+ _instrumentationHost;
1559
+ _drainWaiters = [];
1560
+ constructor(TopState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity) {
1414
1561
  this._instance = instance;
1415
1562
  this._transitionState = undefined;
1416
1563
  this._traceLevel = traceLevel;
@@ -1420,6 +1567,9 @@ export class HsmObject {
1420
1567
  this._jobs = [];
1421
1568
  this._hiPriorityJobs = [];
1422
1569
  this._isRunning = false;
1570
+ this.actorUuid = identity.uuid;
1571
+ this.actorName = identity.name;
1572
+ this.actorPath = identity.path;
1423
1573
  this.topState = TopState;
1424
1574
  this.topStateName = getStateName(TopState);
1425
1575
  this.ctxTypeName = Object.getPrototypeOf(instance.ctx).constructor.name;
@@ -1436,6 +1586,14 @@ export class HsmObject {
1436
1586
  get port() {
1437
1587
  return this._instance.portRef;
1438
1588
  }
1589
+ /** The bound port when it provides a timer service, so service-call timeouts honour a virtual clock. */
1590
+ get callTimer() {
1591
+ const port = this._instance.portRef;
1592
+ if (port !== undefined && typeof port.setTimeout === 'function' && typeof port.clearTimeout === 'function') {
1593
+ return port;
1594
+ }
1595
+ return undefined;
1596
+ }
1439
1597
  get eventName() {
1440
1598
  return this._currentEventName ?? '';
1441
1599
  }
@@ -1483,14 +1641,37 @@ export class HsmObject {
1483
1641
  set traceLevel(traceLevel) {
1484
1642
  this._traceLevel = traceLevel;
1485
1643
  }
1644
+ /**
1645
+ * Resolve when the actor next reaches stability (mailbox fully drained).
1646
+ *
1647
+ * The resolver fires at the queue-drain point — *after* {@link InstrumentationHost.onQueuesDrained}
1648
+ * — so the closing `macrostep.end` and the macrostep-boundary reset are observable before `sync()`
1649
+ * resolves, and a subsequent external stimulus deterministically starts its own macrostep. The
1650
+ * pushed task is a no-op (internal) whose only purpose is to guarantee a drain cycle occurs.
1651
+ */
1486
1652
  sync() {
1487
1653
  return new Promise(resolve => {
1488
- this.pushTask((doneCallback) => {
1489
- resolve();
1654
+ this._drainWaiters.push(resolve);
1655
+ const task = (doneCallback) => {
1490
1656
  doneCallback();
1491
- });
1657
+ };
1658
+ setTaskMeta(task, { internal: true });
1659
+ this.pushTask(task);
1492
1660
  });
1493
1661
  }
1662
+ /** Invoke the user dispatch-error callback, first notifying instrumentation (pure observer). */
1663
+ reportDispatchError(err) {
1664
+ this._instrumentationHost?.onDispatchError(err);
1665
+ this.dispatchErrorCallback(this, err);
1666
+ }
1667
+ flushDrainWaiters() {
1668
+ if (this._drainWaiters.length === 0)
1669
+ return;
1670
+ const waiters = this._drainWaiters;
1671
+ this._drainWaiters = [];
1672
+ for (const resolve of waiters)
1673
+ resolve();
1674
+ }
1494
1675
  pushTask(t) {
1495
1676
  this.enqueueTask(t, this._jobs);
1496
1677
  }
@@ -1518,6 +1699,8 @@ export class HsmObject {
1518
1699
  dequeue() {
1519
1700
  if (this._hiPriorityJobs.length == 0 && this._jobs.length == 0) {
1520
1701
  this._isRunning = false;
1702
+ this._instrumentationHost?.onQueuesDrained();
1703
+ this.flushDrainWaiters();
1521
1704
  return;
1522
1705
  }
1523
1706
  const task = this._hiPriorityJobs.length > 0 ? this._hiPriorityJobs.shift() : this._jobs.shift();
@@ -1527,10 +1710,32 @@ export class HsmObject {
1527
1710
  setTimeout(() => this.runTask(task).then(() => this.dequeue()), 0);
1528
1711
  }
1529
1712
  runTask(task) {
1713
+ this._instrumentationHost?.onTaskBegin(task);
1714
+ let outcome = 'ok';
1530
1715
  return new Promise(resolve => {
1531
- task(() => {
1532
- this.drainHiPriority().then(resolve);
1533
- });
1716
+ const runBody = () => {
1717
+ task(() => {
1718
+ this.drainHiPriority()
1719
+ .then(() => {
1720
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1721
+ resolve();
1722
+ })
1723
+ .catch((_err) => {
1724
+ outcome = 'error';
1725
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1726
+ resolve();
1727
+ });
1728
+ });
1729
+ };
1730
+ try {
1731
+ runBody();
1732
+ }
1733
+ catch (err) {
1734
+ outcome = 'error';
1735
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1736
+ resolve();
1737
+ throw asError(err);
1738
+ }
1534
1739
  });
1535
1740
  }
1536
1741
  drainHiPriority() {
@@ -1558,18 +1763,51 @@ export class HsmObject {
1558
1763
  get traceHeader() {
1559
1764
  return `${this._traceDomainStack.length === 0 ? '' : this._traceDomainStack.join('|') + '|'}`;
1560
1765
  }
1766
+ get traceFrames() {
1767
+ return this._traceDomainStack.map(name => ({ name, kind: classifyFrameKind(name) }));
1768
+ }
1769
+ }
1770
+ /** Best-effort classification of a live trace-domain name into a structured {@link TraceFrame.kind}. */
1771
+ function classifyFrameKind(name) {
1772
+ if (name.startsWith('#'))
1773
+ return 'event';
1774
+ if (name === 'execute')
1775
+ return 'handler';
1776
+ if (name === 'initialize' || name.startsWith('initialize'))
1777
+ return 'initialize';
1778
+ if (name.startsWith('transition'))
1779
+ return 'transition';
1780
+ if (name.includes('onEntry'))
1781
+ return 'onEntry';
1782
+ if (name.includes('onExit'))
1783
+ return 'onExit';
1784
+ return 'handler';
1561
1785
  }
1562
1786
  //#region machine
1563
1787
  export class Machine extends HsmObject {
1564
1788
  transitionResolver;
1789
+ identity;
1790
+ instrumentation;
1565
1791
  _dispatchStrategy;
1566
1792
  protocolIndex;
1567
1793
  handlerFacade;
1568
1794
  selfActor;
1569
1795
  selfImmediate;
1570
1796
  actorFacades = new Map();
1571
- constructor(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, transitionResolver) {
1572
- super(topState, instance, traceWriter, traceLevel, dispatchErrorCallback);
1797
+ _macrostepCounter = 0;
1798
+ _currentMacrostep;
1799
+ _microstepFromState;
1800
+ _childSpawnCounters = new Map();
1801
+ _nextTraceCallId = 0;
1802
+ _proxiedPort;
1803
+ constructor(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, identity, instrumentation, transitionResolver) {
1804
+ super(topState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity);
1805
+ this.identity = identity;
1806
+ this.instrumentation = instrumentation;
1807
+ if (instrumentation !== undefined) {
1808
+ this._instrumentationHost = this;
1809
+ notifyActorCreated(instrumentation, identity);
1810
+ }
1573
1811
  Object.defineProperty(instance, kHandlerMachine, { value: this, enumerable: false, writable: false, configurable: false });
1574
1812
  this.protocolIndex = protocolIndex;
1575
1813
  cacheProtocolIndex(topState, protocolIndex);
@@ -1583,7 +1821,227 @@ export class Machine extends HsmObject {
1583
1821
  Object.defineProperty(instance, 'notifyNow', { value: this.selfImmediate, enumerable: true, configurable: true });
1584
1822
  this.bindPort(instance.portRef);
1585
1823
  if (initialize) {
1586
- this.pushTask(createInitTask(this, this.transitionResolver));
1824
+ const initTask = createInitTask(this, this.transitionResolver);
1825
+ setTaskMeta(initTask, { event: 'initialize', queue: 'default', triggerKind: 'init', internal: false });
1826
+ this.pushTask(initTask);
1827
+ }
1828
+ }
1829
+ allocateChildSpawnIndex(childTopName) {
1830
+ const childName = actorNameFromTopState(childTopName);
1831
+ const index = this._childSpawnCounters.get(childName) ?? 0;
1832
+ this._childSpawnCounters.set(childName, index + 1);
1833
+ return index;
1834
+ }
1835
+ needsDispatchContext() {
1836
+ return this.instrumentation !== undefined || this.traceLevel !== TraceLevel.PRODUCTION;
1837
+ }
1838
+ buildDispatchToken() {
1839
+ return {
1840
+ machine: this,
1841
+ actorUuid: this.actorUuid,
1842
+ macrostepId: this._currentMacrostep?.id,
1843
+ stepSeq: this._currentMacrostep?.stepSeq,
1844
+ };
1845
+ }
1846
+ readDispatchCause(kind) {
1847
+ const storage = dispatchContext.get();
1848
+ const token = storage?.getStore();
1849
+ if (token?.actorUuid === undefined)
1850
+ return undefined;
1851
+ return {
1852
+ actorUuid: token.actorUuid,
1853
+ macrostepId: token.macrostepId,
1854
+ stepSeq: token.stepSeq,
1855
+ kind,
1856
+ };
1857
+ }
1858
+ nextTraceCallId() {
1859
+ this._nextTraceCallId += 1;
1860
+ return this._nextTraceCallId;
1861
+ }
1862
+ beginPortCall(method) {
1863
+ if (this.instrumentation === undefined)
1864
+ return undefined;
1865
+ const cause = this.readDispatchCause('wire');
1866
+ const info = {
1867
+ callId: this.nextTraceCallId(),
1868
+ method,
1869
+ cause,
1870
+ };
1871
+ notifyPortCallBegin(this.instrumentation, info);
1872
+ return info;
1873
+ }
1874
+ endPortCall(begin, outcome, error) {
1875
+ if (this.instrumentation === undefined || begin === undefined)
1876
+ return;
1877
+ const info = {
1878
+ callId: begin.callId,
1879
+ method: begin.method,
1880
+ outcome,
1881
+ error,
1882
+ };
1883
+ notifyPortCallEnd(this.instrumentation, info);
1884
+ }
1885
+ beginOutboundCall(service, targetUuid) {
1886
+ if (this.instrumentation === undefined)
1887
+ return undefined;
1888
+ const portToken = currentPortCallToken();
1889
+ const cause = this.readDispatchCause('wire') ?? portToken?.cause;
1890
+ const info = {
1891
+ callId: this.nextTraceCallId(),
1892
+ service,
1893
+ targetUuid,
1894
+ cause,
1895
+ };
1896
+ notifyOutboundCallBegin(this.instrumentation, info);
1897
+ return info;
1898
+ }
1899
+ endOutboundCall(begin, outcome, error) {
1900
+ if (this.instrumentation === undefined || begin === undefined)
1901
+ return;
1902
+ const info = {
1903
+ callId: begin.callId,
1904
+ service: begin.service,
1905
+ outcome,
1906
+ error,
1907
+ };
1908
+ notifyOutboundCallEnd(this.instrumentation, info);
1909
+ }
1910
+ slotBucket(eventName) {
1911
+ return this.protocolIndex.get(eventName)?.bucket ?? 'notifications';
1912
+ }
1913
+ onTaskBegin(task) {
1914
+ const meta = getTaskMeta(task);
1915
+ if (meta?.internal === true || this.instrumentation === undefined)
1916
+ return;
1917
+ if (this._currentMacrostep === undefined) {
1918
+ const id = `${this.actorUuid}:${++this._macrostepCounter}`;
1919
+ const trigger = meta?.event ?? 'unknown';
1920
+ const triggerKind = meta?.triggerKind ?? (this._jobs.length + this._hiPriorityJobs.length > 0 ? 'self' : 'external');
1921
+ this._currentMacrostep = {
1922
+ id,
1923
+ trigger,
1924
+ triggerKind,
1925
+ startState: this.currentStateName,
1926
+ stepSeq: -1,
1927
+ transitioned: false,
1928
+ outcome: 'ok',
1929
+ cause: meta?.cause,
1930
+ };
1931
+ notifyMacrostepBegin(this.instrumentation, {
1932
+ id,
1933
+ actor: this.identity,
1934
+ trigger,
1935
+ triggerKind,
1936
+ startState: this.currentStateName,
1937
+ cause: meta?.cause,
1938
+ delayMs: meta?.delayMs,
1939
+ });
1940
+ }
1941
+ const macrostep = this._currentMacrostep;
1942
+ macrostep.stepSeq += 1;
1943
+ const seq = macrostep.stepSeq;
1944
+ const fromState = this.currentStateName;
1945
+ this._microstepFromState = fromState;
1946
+ // Stamp seq + fromState on the task so onTaskEnd pairs correctly even when nested priority
1947
+ // drains mutate the shared macrostep counter between this task's begin and end.
1948
+ if (meta !== undefined) {
1949
+ meta.seq = seq;
1950
+ meta.fromState = fromState;
1951
+ }
1952
+ else {
1953
+ setTaskMeta(task, { seq, fromState });
1954
+ }
1955
+ const eventName = meta?.event ?? this.eventName ?? 'unknown';
1956
+ const handlerState = lookupHandlerState(this, eventName);
1957
+ const storage = dispatchContext.get();
1958
+ const runMicrostep = () => {
1959
+ notifyMicrostepBegin(this.instrumentation, {
1960
+ macrostepId: macrostep.id,
1961
+ seq,
1962
+ event: eventName,
1963
+ bucket: this.slotBucket(eventName),
1964
+ queue: meta?.queue ?? 'default',
1965
+ fromState: this.currentStateName,
1966
+ handlerState,
1967
+ cause: meta?.cause,
1968
+ });
1969
+ };
1970
+ if (storage !== undefined && this.needsDispatchContext()) {
1971
+ storage.run(this.buildDispatchToken(), runMicrostep);
1972
+ }
1973
+ else {
1974
+ runMicrostep();
1975
+ }
1976
+ }
1977
+ onTaskEnd(task, outcome) {
1978
+ const meta = getTaskMeta(task);
1979
+ if (meta?.internal === true || this.instrumentation === undefined || this._currentMacrostep === undefined)
1980
+ return;
1981
+ const macrostep = this._currentMacrostep;
1982
+ const fromState = meta?.fromState ?? this._microstepFromState ?? macrostep.startState;
1983
+ const seq = meta?.seq ?? macrostep.stepSeq;
1984
+ const transitioned = this.currentStateName !== fromState;
1985
+ if (transitioned)
1986
+ macrostep.transitioned = true;
1987
+ if (outcome === 'error')
1988
+ macrostep.outcome = 'error';
1989
+ notifyMicrostepEnd(this.instrumentation, {
1990
+ macrostepId: macrostep.id,
1991
+ seq,
1992
+ toState: this.currentStateName,
1993
+ transitioned,
1994
+ async: false,
1995
+ outcome,
1996
+ });
1997
+ }
1998
+ onDispatchError(err) {
1999
+ if (this.instrumentation === undefined)
2000
+ return;
2001
+ notifyError(this.instrumentation, {
2002
+ phase: errorPhaseFromError(err),
2003
+ errorClass: err.name,
2004
+ error: err,
2005
+ recovered: false,
2006
+ });
2007
+ }
2008
+ onQueuesDrained() {
2009
+ if (this.instrumentation === undefined || this._currentMacrostep === undefined)
2010
+ return;
2011
+ const macrostep = this._currentMacrostep;
2012
+ notifyMacrostepEnd(this.instrumentation, {
2013
+ id: macrostep.id,
2014
+ endState: this.currentStateName,
2015
+ steps: macrostep.stepSeq + 1,
2016
+ transitioned: macrostep.transitioned,
2017
+ outcome: macrostep.outcome,
2018
+ });
2019
+ this._currentMacrostep = undefined;
2020
+ }
2021
+ resolveEnqueueCause() {
2022
+ const inherited = this.readDispatchCause('message');
2023
+ if (inherited === undefined) {
2024
+ return { actorUuid: this.actorUuid, kind: 'cause' };
2025
+ }
2026
+ if (inherited.actorUuid !== this.actorUuid) {
2027
+ return inherited;
2028
+ }
2029
+ return { ...inherited, kind: 'cause' };
2030
+ }
2031
+ enqueueWithInstrumentation(task, event, queue, triggerKind) {
2032
+ const cause = this.resolveEnqueueCause();
2033
+ setTaskMeta(task, { event, queue, cause, triggerKind });
2034
+ notifyEnqueue(this.instrumentation, {
2035
+ event,
2036
+ queue,
2037
+ cause,
2038
+ targetUuid: this.actorUuid,
2039
+ });
2040
+ if (queue === 'priority') {
2041
+ this.pushHiPriorityTask(task);
2042
+ }
2043
+ else {
2044
+ this.pushTask(task);
1587
2045
  }
1588
2046
  }
1589
2047
  get traceLevel() {
@@ -1603,12 +2061,22 @@ export class Machine extends HsmObject {
1603
2061
  }
1604
2062
  this.recordObserverEvent(name, args);
1605
2063
  return new Promise((resolve, reject) => {
1606
- this.pushTask(createServiceTask(this, this.transitionResolver, name, args, resolve, reject));
2064
+ const task = createServiceTask(this, this.transitionResolver, name, args, resolve, reject);
2065
+ if (this.instrumentation !== undefined) {
2066
+ this.enqueueWithInstrumentation(task, name, 'default', 'call');
2067
+ return;
2068
+ }
2069
+ this.pushTask(task);
1607
2070
  });
1608
2071
  }
1609
2072
  dispatchNotification(name, args, queue) {
1610
2073
  this.recordObserverEvent(name, args);
1611
2074
  const task = createNotificationTask(this, this.transitionResolver, name, args);
2075
+ if (this.instrumentation !== undefined) {
2076
+ const triggerKind = this._currentMacrostep === undefined ? 'external' : 'self';
2077
+ this.enqueueWithInstrumentation(task, name, queue, triggerKind);
2078
+ return;
2079
+ }
1612
2080
  if (queue === 'priority') {
1613
2081
  this.pushHiPriorityTask(task);
1614
2082
  }
@@ -1625,14 +2093,60 @@ export class Machine extends HsmObject {
1625
2093
  return facade;
1626
2094
  }
1627
2095
  scheduleNotification(ms, name, args) {
1628
- const enqueue = () => {
1629
- this.dispatchNotification(name, args, 'default');
1630
- };
1631
2096
  const port = this._instance.portRef;
1632
2097
  if (port === undefined) {
1633
2098
  throw new Error('ihsm: deferred notification requires a port');
1634
2099
  }
1635
- port.setTimeout(enqueue, ms);
2100
+ if (this.instrumentation === undefined) {
2101
+ port.setTimeout(() => this.dispatchNotification(name, args, 'default'), ms);
2102
+ return;
2103
+ }
2104
+ // Capture the arming step's dispatch token now (the timer fires later while the actor is idle,
2105
+ // so the ambient token is gone by then). The fired macrostep links back to it as `timer` (§5.3.1).
2106
+ const armed = this.readDispatchCause('timer');
2107
+ const cause = armed ?? { actorUuid: this.actorUuid, kind: 'timer' };
2108
+ port.setTimeout(() => this.enqueueTimerNotification(name, args, cause, ms), ms);
2109
+ }
2110
+ enqueueTimerNotification(name, args, cause, delayMs) {
2111
+ this.recordObserverEvent(name, args);
2112
+ const task = createNotificationTask(this, this.transitionResolver, name, args);
2113
+ setTaskMeta(task, { event: name, queue: 'default', cause, triggerKind: 'timer', delayMs });
2114
+ notifyEnqueue(this.instrumentation, { event: name, queue: 'default', cause, delayMs, targetUuid: this.actorUuid });
2115
+ this.pushTask(task);
2116
+ }
2117
+ _actorLogger;
2118
+ /** Severity-typed handler logger surfaced as `this.hsm.log.*` (CORE-F, §4.10.1). */
2119
+ get logger() {
2120
+ if (this._actorLogger === undefined) {
2121
+ const emit = (severity, message, attributes) => this.emitUserLog(severity, message, attributes);
2122
+ this._actorLogger = {
2123
+ trace: (m, a) => emit('trace', m, a),
2124
+ debug: (m, a) => emit('debug', m, a),
2125
+ info: (m, a) => emit('info', m, a),
2126
+ warn: (m, a) => emit('warn', m, a),
2127
+ error: (m, a) => emit('error', m, a),
2128
+ fatal: (m, a) => emit('fatal', m, a),
2129
+ };
2130
+ }
2131
+ return this._actorLogger;
2132
+ }
2133
+ emitUserLog(severity, message, attributes) {
2134
+ const isError = message instanceof Error;
2135
+ const text = isError ? message.message : message;
2136
+ const body = `${this.traceHeader}${this.currentStateName}: ${text}`;
2137
+ // User logs fire on intent and are never TraceLevel-gated; mirror to the TraceWriter for console.
2138
+ this.traceWriter.write(this, body);
2139
+ if (this.instrumentation === undefined)
2140
+ return;
2141
+ const record = {
2142
+ severity,
2143
+ body,
2144
+ attributes,
2145
+ frames: this.traceFrames,
2146
+ error: isError ? message : undefined,
2147
+ source: 'user',
2148
+ };
2149
+ notifyLog(this.instrumentation, record);
1636
2150
  }
1637
2151
  /** @internal Binds deferred self-notifications to a port instance. */
1638
2152
  bindPort(portRef) {
@@ -1648,7 +2162,50 @@ export class Machine extends HsmObject {
1648
2162
  },
1649
2163
  transition: next => machine.transition(next),
1650
2164
  get port() {
1651
- return instance.portRef;
2165
+ const rawPort = instance.portRef;
2166
+ if (machine.instrumentation === undefined || rawPort === undefined || typeof rawPort !== 'object') {
2167
+ return rawPort;
2168
+ }
2169
+ if (machine._proxiedPort !== undefined)
2170
+ return machine._proxiedPort;
2171
+ const proxy = new Proxy(rawPort, {
2172
+ get(target, prop, receiver) {
2173
+ const value = Reflect.get(target, prop, receiver);
2174
+ if (typeof value !== 'function')
2175
+ return value;
2176
+ const method = String(prop);
2177
+ return (...args) => {
2178
+ const begin = machine.beginPortCall(method);
2179
+ if (begin === undefined) {
2180
+ return Reflect.apply(value, target, args);
2181
+ }
2182
+ const call = () => Reflect.apply(value, target, args);
2183
+ const storage = portCallContext.get();
2184
+ try {
2185
+ const result = storage !== undefined ? storage.run({ machine, callId: begin.callId, method, cause: begin.cause }, call) : call();
2186
+ if (result instanceof Promise) {
2187
+ return result.then((value) => {
2188
+ machine.endPortCall(begin, 'ok');
2189
+ return value;
2190
+ }, (cause) => {
2191
+ const err = asError(cause);
2192
+ machine.endPortCall(begin, 'error', err);
2193
+ throw err;
2194
+ });
2195
+ }
2196
+ machine.endPortCall(begin, 'ok');
2197
+ return result;
2198
+ }
2199
+ catch (cause) {
2200
+ const err = asError(cause);
2201
+ machine.endPortCall(begin, 'error', err);
2202
+ throw err;
2203
+ }
2204
+ };
2205
+ },
2206
+ });
2207
+ machine._proxiedPort = proxy;
2208
+ return proxy;
1652
2209
  },
1653
2210
  unhandled: () => machine.unhandled(),
1654
2211
  get eventName() {
@@ -1672,6 +2229,24 @@ export class Machine extends HsmObject {
1672
2229
  get traceHeader() {
1673
2230
  return machine.traceHeader;
1674
2231
  },
2232
+ get traceFrames() {
2233
+ return machine.traceFrames;
2234
+ },
2235
+ get log() {
2236
+ return machine.logger;
2237
+ },
2238
+ get id() {
2239
+ return machine.actorUuid;
2240
+ },
2241
+ get actorUuid() {
2242
+ return machine.actorUuid;
2243
+ },
2244
+ get actorName() {
2245
+ return machine.actorName;
2246
+ },
2247
+ get actorPath() {
2248
+ return machine.actorPath;
2249
+ },
1675
2250
  get traceLevel() {
1676
2251
  return machine.traceLevel;
1677
2252
  },
@@ -1739,6 +2314,18 @@ export class Machine extends HsmObject {
1739
2314
  get traceHeader() {
1740
2315
  return machine.traceHeader;
1741
2316
  },
2317
+ get id() {
2318
+ return machine.actorUuid;
2319
+ },
2320
+ get actorUuid() {
2321
+ return machine.actorUuid;
2322
+ },
2323
+ get actorName() {
2324
+ return machine.actorName;
2325
+ },
2326
+ get actorPath() {
2327
+ return machine.actorPath;
2328
+ },
1742
2329
  };
1743
2330
  if (includeState) {
1744
2331
  Object.defineProperties(facade, {
@@ -1808,9 +2395,16 @@ export function defaultDispatchErrorCallback(hsm, err) {
1808
2395
  throw err;
1809
2396
  }
1810
2397
  /** @internal Spawn with embodiment kind — used by factories and `ihsm/testing`. */
1811
- export function spawnActor(kind, topState, ctx, port, options) {
2398
+ export function spawnActor(kind, topState, ctx, port, options, spawnContext = {}) {
1812
2399
  const { initialize = defaultInitialize, traceLevel = TraceLevel.DEBUG, traceWriter = defaultTraceWriter, dispatchErrorCallback = defaultDispatchErrorCallback, transitions } = options;
1813
2400
  const protocolIndex = buildProtocolIndex(topState);
2401
+ const topStateName = getStateName(topState);
2402
+ const parentMachine = spawnContext.parentMachine;
2403
+ // Tracing is a cross-cutting concern: the actor adopts the globally-registered collector(s) at
2404
+ // spawn (no per-actor `instrumentation` option). Snapshotting here keeps "no collector" actors
2405
+ // at zero overhead while still sharing one collector instance across a parent and its children.
2406
+ const resolvedInstrumentation = getActiveInstrumentation();
2407
+ const identity = parentMachine !== undefined ? mintActorIdentity('child', childActorPath(parentMachine.actorPath, topStateName, parentMachine.allocateChildSpawnIndex(topStateName)), parentMachine.actorUuid) : mintActorIdentity(kind, rootActorPath(topStateName));
1814
2408
  const boundPort = (port ?? new Port());
1815
2409
  const instance = {
1816
2410
  ctx,
@@ -1818,7 +2412,26 @@ export function spawnActor(kind, topState, ctx, port, options) {
1818
2412
  portRef: boundPort,
1819
2413
  };
1820
2414
  Object.setPrototypeOf(instance, topState.prototype);
1821
- const machine = new Machine(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, transitions ?? new RuntimeTransitionResolver());
2415
+ const machine = new Machine(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, identity, resolvedInstrumentation, transitions ?? new RuntimeTransitionResolver());
2416
+ if (resolvedInstrumentation !== undefined) {
2417
+ const token = dispatchContext.get()?.getStore();
2418
+ const parentCause = token?.actorUuid !== undefined
2419
+ ? {
2420
+ actorUuid: token.actorUuid,
2421
+ macrostepId: token.macrostepId,
2422
+ stepSeq: token.stepSeq,
2423
+ kind: 'spawn',
2424
+ }
2425
+ : {
2426
+ actorUuid: identity.parentUuid ?? identity.uuid,
2427
+ kind: 'spawn',
2428
+ };
2429
+ const spawnInfo = {
2430
+ parent: parentCause,
2431
+ child: identity,
2432
+ };
2433
+ notifyActorSpawned(resolvedInstrumentation, spawnInfo);
2434
+ }
1822
2435
  const portKind = isRequestingPort(boundPort) ? 'child' : kind === 'root' ? 'inbound' : kind;
1823
2436
  boundPort.actor = createActorHandle(machine, topState, protocolIndex, portKind);
1824
2437
  return createActorHandle(machine, topState, protocolIndex, kind);
@@ -1839,9 +2452,11 @@ export function asParentActor(handler) {
1839
2452
  }
1840
2453
  /** Parent composes a child machine — returns full child protocol shell with `parent` set. */
1841
2454
  export function makeChildActor(parent, childTop, childCtx, port, options = {}) {
1842
- const child = spawnActor('child', childTop, childCtx, port, options);
2455
+ const parentMachine = parent[kParentLink];
2456
+ const child = spawnActor('child', childTop, childCtx, port, options, { parentMachine: parentMachine });
1843
2457
  Object.defineProperty(child, 'parent', { value: parent, enumerable: true, writable: false, configurable: true });
1844
2458
  return child;
1845
2459
  }
1846
2460
  export { kHandlerMachine, kParentLink } from './types.js';
2461
+ export { configureRunSeed, getRunSeed, getRunNamespace, actorNameFromTopState, mintActorIdentity, rootActorPath, childActorPath } from './identity.js';
1847
2462
  //# sourceMappingURL=runtime.js.map