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,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.kParentLink = exports.kHandlerMachine = exports.defaultInitialize = exports.defaultTraceWriter = exports.Machine = exports.HsmObject = exports.RuntimeTransitionResolver = exports.TransitionTableError = exports.dispatchContext = exports.SelfCallDeadlockError = exports.kMachine = exports.CallTimeoutError = exports.ReservedNames = exports.StateGraph = exports.ProtocolCollisionError = exports.FatalErrorState = exports.InitializationError = exports.FatalError = exports.InitialStateError = exports.UnhandledEventError = exports.EventHandlerError = exports.TransitionError = exports.RuntimeError = exports.HsmError = exports.TopState = exports.RequestingPort = exports.Port = exports.TraceLevel = void 0;
3
+ exports.childActorPath = exports.rootActorPath = exports.mintActorIdentity = exports.actorNameFromTopState = exports.getRunNamespace = exports.getRunSeed = exports.configureRunSeed = exports.kParentLink = exports.kHandlerMachine = exports.defaultInitialize = exports.defaultTraceWriter = exports.Machine = exports.HsmObject = exports.RuntimeTransitionResolver = exports.TransitionTableError = exports.dispatchContext = exports.SelfCallDeadlockError = exports.kMachine = exports.CallTimeoutError = exports.ReservedNames = exports.StateGraph = exports.ProtocolCollisionError = exports.FatalErrorState = exports.InitializationError = exports.FatalError = exports.InitialStateError = exports.UnhandledEventError = exports.EventHandlerError = exports.TransitionError = exports.RuntimeError = exports.HsmError = exports.TopState = exports.RequestingPort = exports.Port = exports.TraceLevel = void 0;
4
4
  exports.asError = asError;
5
5
  exports.quoteUnknown = quoteUnknown;
6
6
  exports.quoteError = quoteError;
@@ -9,6 +9,7 @@ exports.hasInitialState = hasInitialState;
9
9
  exports.getTransitionKey = getTransitionKey;
10
10
  exports.defineStateName = defineStateName;
11
11
  exports.getStateName = getStateName;
12
+ exports.lookupHandlerState = lookupHandlerState;
12
13
  exports.lookupEventHandler = lookupEventHandler;
13
14
  exports.InitialState = InitialState;
14
15
  exports.registerStateNames = registerStateNames;
@@ -21,6 +22,7 @@ exports.serviceCallWithTimeout = serviceCallWithTimeout;
21
22
  exports.createActorHandle = createActorHandle;
22
23
  exports.getSelfNotificationsProto = getSelfNotificationsProto;
23
24
  exports.createSelfNotifications = createSelfNotifications;
25
+ exports.currentTraceAnchor = currentTraceAnchor;
24
26
  exports.planTransitionClasses = planTransitionClasses;
25
27
  exports.executeTransitionRoutine = executeTransitionRoutine;
26
28
  exports.createTransitionTracer = createTransitionTracer;
@@ -36,6 +38,8 @@ exports.makeActor = makeActor;
36
38
  exports.asParentActor = asParentActor;
37
39
  exports.makeChildActor = makeChildActor;
38
40
  const types_1 = require("./types");
41
+ const identity_1 = require("./identity");
42
+ const instrumentation_1 = require("./instrumentation");
39
43
  //#region TraceLevel
40
44
  var TraceLevel;
41
45
  (function (TraceLevel) {
@@ -269,6 +273,20 @@ exports.FatalErrorState = FatalErrorState;
269
273
  defineStateName(TopState, 'TopState');
270
274
  defineStateName(FatalErrorState, 'FatalErrorState');
271
275
  /** @internal */
276
+ function lookupHandlerState(hsm, eventName) {
277
+ let state = hsm.currentState;
278
+ while (true) {
279
+ const prototype = state.prototype;
280
+ if (Object.prototype.hasOwnProperty.call(prototype, eventName)) {
281
+ return getStateName(state);
282
+ }
283
+ if (state === TopState) {
284
+ return undefined;
285
+ }
286
+ state = Object.getPrototypeOf(state);
287
+ }
288
+ }
289
+ /** @internal */
272
290
  function lookupEventHandler(hsm, eventName) {
273
291
  let state = hsm.currentState;
274
292
  while (true) {
@@ -496,19 +514,29 @@ function splitServiceArgs(args) {
496
514
  }
497
515
  return { callArgs: args.slice(0, -1), timeoutMs };
498
516
  }
499
- function serviceCallWithTimeout(promise, method, timeoutMs) {
517
+ /**
518
+ * Race a service-call promise against a `timeoutMs` deadline.
519
+ *
520
+ * The deadline is armed through `timer` — the actor's port timer service — so that under a
521
+ * {@link TestPort} virtual clock a call timeout is driven by `port.advance(...)` and stays
522
+ * fully deterministic. When no port timer is available the host timer is used (production
523
+ * `Port` already delegates to the host timer, so behaviour there is unchanged).
524
+ */
525
+ function serviceCallWithTimeout(promise, method, timeoutMs, timer) {
500
526
  if (timeoutMs === 0) {
501
527
  return Promise.reject(new CallTimeoutError(method));
502
528
  }
529
+ const arm = (callback) => (timer !== undefined ? timer.setTimeout(callback, timeoutMs) : globalThis.setTimeout(callback, timeoutMs));
530
+ const disarm = (handle) => (timer !== undefined ? timer.clearTimeout(handle) : globalThis.clearTimeout(handle));
503
531
  return new Promise((resolve, reject) => {
504
- const timer = setTimeout(() => {
532
+ const handle = arm(() => {
505
533
  reject(new CallTimeoutError(method));
506
- }, timeoutMs);
534
+ });
507
535
  promise.then(value => {
508
- clearTimeout(timer);
536
+ disarm(handle);
509
537
  resolve(value);
510
538
  }, err => {
511
- clearTimeout(timer);
539
+ disarm(handle);
512
540
  reject(err);
513
541
  });
514
542
  });
@@ -540,8 +568,17 @@ function getFacetProto(topState, index, kind, facet) {
540
568
  if (facet === 'call') {
541
569
  built[name] = function (...args) {
542
570
  const { callArgs, timeoutMs } = splitServiceArgs(args);
543
- const promise = this[exports.kMachine].dispatchService(name, callArgs);
544
- return timeoutMs === undefined ? promise : serviceCallWithTimeout(promise, name, timeoutMs);
571
+ const machine = this[exports.kMachine];
572
+ const begin = machine.beginOutboundCall?.(name, machine.actorUuid);
573
+ const promise = machine.dispatchService(name, callArgs).then(value => {
574
+ machine.endOutboundCall?.(begin, 'ok');
575
+ return value;
576
+ }, cause => {
577
+ const err = asError(cause);
578
+ machine.endOutboundCall?.(begin, 'error', err);
579
+ throw err;
580
+ });
581
+ return timeoutMs === undefined ? promise : serviceCallWithTimeout(promise, name, timeoutMs, machine.callTimer);
545
582
  };
546
583
  }
547
584
  else {
@@ -578,6 +615,12 @@ function createActorHandle(machine, topState, index, kind) {
578
615
  Object.defineProperty(handle, 'notify', { value: createFacet(machine, topState, index, kind, 'notify'), enumerable: true });
579
616
  Object.defineProperty(handle, 'notifyNow', { value: createFacet(machine, topState, index, kind, 'notifyNow'), enumerable: true });
580
617
  Object.defineProperty(handle, 'call', { value: createFacet(machine, topState, index, kind, 'call'), enumerable: true });
618
+ Object.defineProperty(handle, 'id', {
619
+ enumerable: true,
620
+ get() {
621
+ return machine.actorUuid;
622
+ },
623
+ });
581
624
  handle.hsm = machine.actorHsmFor(kind);
582
625
  return handle;
583
626
  }
@@ -620,6 +663,18 @@ class SelfCallDeadlockError extends Error {
620
663
  }
621
664
  }
622
665
  exports.SelfCallDeadlockError = SelfCallDeadlockError;
666
+ function errorPhaseFromError(err) {
667
+ if (err instanceof TransitionError) {
668
+ return err.failedCallback === 'onEntry' ? 'onEntry' : 'onExit';
669
+ }
670
+ if (err instanceof UnhandledEventError)
671
+ return 'unhandled';
672
+ if (err instanceof InitializationError)
673
+ return 'initialize';
674
+ if (err instanceof EventHandlerError)
675
+ return 'handler';
676
+ return 'handler';
677
+ }
623
678
  /**
624
679
  * Lazy Node AsyncLocalStorage for non-production deadlock detection.
625
680
  * State lives in a closure — no exported mutable slot.
@@ -654,6 +709,45 @@ exports.dispatchContext = (() => {
654
709
  }
655
710
  return { get, resetInit, markUnavailable };
656
711
  })();
712
+ /**
713
+ * Best-effort current runtime trace anchor (`actorUuid`, `macrostepId`, `stepSeq`) for user code.
714
+ * Returns `undefined` when called outside an active handler dispatch turn.
715
+ */
716
+ function currentTraceAnchor() {
717
+ const token = exports.dispatchContext.get()?.getStore();
718
+ if (token?.actorUuid === undefined)
719
+ return undefined;
720
+ return {
721
+ actorUuid: token.actorUuid,
722
+ macrostepId: token.macrostepId,
723
+ stepSeq: token.stepSeq,
724
+ };
725
+ }
726
+ /** Lazy ALS carrying the currently-executing proxied port call (when instrumentation is active). */
727
+ const portCallContext = (() => {
728
+ let storage = undefined;
729
+ function get() {
730
+ if (storage !== undefined) {
731
+ return storage ?? undefined;
732
+ }
733
+ if (typeof process === 'undefined' || process.versions?.node === undefined) {
734
+ storage = null;
735
+ return undefined;
736
+ }
737
+ try {
738
+ // Dynamic require — keeps browser bundles free of `node:async_hooks`.
739
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
740
+ const hooks = require('node:async_hooks');
741
+ storage = new hooks.AsyncLocalStorage();
742
+ return storage;
743
+ }
744
+ catch {
745
+ storage = null;
746
+ return undefined;
747
+ }
748
+ }
749
+ return { get };
750
+ })();
657
751
  //#region transition-routines
658
752
  /** Thrown when a generated transition table's graph hash does not match the scanned hierarchy. */
659
753
  class TransitionTableError extends Error {
@@ -705,10 +799,14 @@ function planTransitionClasses(srcState, destState) {
705
799
  }
706
800
  return { exit: srcPath, entry: dstPath, finalState };
707
801
  }
708
- async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer) {
802
+ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer, hookEvents) {
709
803
  const statePrototype = state.prototype;
710
804
  const stateName = getStateName(state);
711
805
  const hasHook = Object.prototype.hasOwnProperty.call(statePrototype, hook);
806
+ // Emit hook tracer callbacks for real (own) hooks at verbose, or always for a structural seam
807
+ // (instrumentation) whose entry/exit spans are not TraceLevel-gated (spec §4.8). Skipped default
808
+ // hooks are never eventized.
809
+ const emitHookEvents = (style === 'verbose' || hookEvents) && hasHook;
712
810
  if ((style === 'verbose' || style === 'debug') && !hasHook) {
713
811
  if (style === 'verbose') {
714
812
  tracer?.traceHookSkipped(stateName, hook);
@@ -716,16 +814,19 @@ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, to
716
814
  return;
717
815
  }
718
816
  try {
817
+ if (emitHookEvents) {
818
+ tracer?.traceHookStart?.(stateName, hook);
819
+ }
719
820
  const res = statePrototype[hook].call(instance);
720
821
  if (res) {
721
822
  await res;
722
823
  }
723
- if (style === 'verbose') {
824
+ if (emitHookEvents) {
724
825
  tracer?.traceHookDone(stateName, hook);
725
826
  }
726
827
  }
727
828
  catch (cause) {
728
- if (style === 'verbose') {
829
+ if (emitHookEvents) {
729
830
  tracer?.traceHookError(stateName, hook, cause);
730
831
  }
731
832
  throw new TransitionError(hsm, asError(cause), stateName, hook, fromStateName, toStateName);
@@ -739,16 +840,27 @@ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, to
739
840
  async function executeTransitionRoutine(hsm, instance, plan, srcState, dstState, options = {}) {
740
841
  const style = options.style ?? 'production';
741
842
  const tracer = options.tracer;
843
+ const hookEvents = options.hookEvents ?? false;
742
844
  const fromStateName = getStateName(srcState);
743
845
  const toStateName = getStateName(dstState);
744
- if (style === 'verbose' || style === 'debug') {
745
- tracer?.traceTransitionStart(fromStateName, toStateName);
746
- }
846
+ // The structural transition span fires whenever a tracer is attached (the instrumentation seam is
847
+ // not TraceLevel-gated); the console tracer is only ever supplied off-PRODUCTION, so this is a
848
+ // no-op there unless an explicit instrumentation tracer is present.
849
+ tracer?.traceTransitionStart(fromStateName, toStateName);
747
850
  for (const state of plan.exit) {
748
- await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer);
851
+ await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer, hookEvents);
749
852
  }
853
+ let initializeOpened = false;
750
854
  for (const state of plan.entry) {
751
- await invokeLifecycleHook(hsm, instance, state, 'onEntry', fromStateName, toStateName, style, tracer);
855
+ if (!initializeOpened && state !== dstState) {
856
+ tracer?.traceInitializeStart?.(toStateName);
857
+ initializeOpened = true;
858
+ }
859
+ await invokeLifecycleHook(hsm, instance, state, 'onEntry', fromStateName, toStateName, style, tracer, hookEvents);
860
+ }
861
+ if (initializeOpened) {
862
+ const finalName = plan.finalState !== undefined ? getStateName(plan.finalState) : toStateName;
863
+ tracer?.traceInitializeDone?.(finalName);
752
864
  }
753
865
  const applyState = (next) => {
754
866
  if (options.setCurrentState) {
@@ -771,6 +883,8 @@ async function executeTransitionRoutine(hsm, instance, plan, srcState, dstState,
771
883
  }
772
884
  if (plan.finalState) {
773
885
  applyState(plan.finalState);
886
+ // Close the structural transition span on the PRODUCTION path (verbose/debug returned above).
887
+ tracer?.traceTransitionDone(getStateName(plan.finalState));
774
888
  }
775
889
  }
776
890
  function createTransitionTracer(hsm) {
@@ -778,6 +892,12 @@ function createTransitionTracer(hsm) {
778
892
  traceTransitionStart(fromStateName, toStateName) {
779
893
  hsm._tracePush(`transition from ${fromStateName} to ${toStateName}`, `started transition from ${fromStateName} to ${toStateName} `);
780
894
  },
895
+ traceInitializeStart(stateName) {
896
+ hsm._tracePush(`initialize ${stateName}`, `started initialize drill-down from ${stateName}`);
897
+ },
898
+ traceInitializeDone(finalStateName) {
899
+ hsm._tracePopDone(`done initialize drill-down at ${finalStateName}`);
900
+ },
781
901
  traceHookDone(stateName, hook) {
782
902
  hsm._traceWrite(`${stateName}.${hook}() done`);
783
903
  },
@@ -809,9 +929,15 @@ class RuntimeTransitionRoutine {
809
929
  }
810
930
  async execute(hsm, srcState, dstState) {
811
931
  const style = hsm.traceLevel === TraceLevel.PRODUCTION ? 'production' : hsm.traceLevel === TraceLevel.DEBUG ? 'debug' : 'verbose';
932
+ const machine = hsm;
933
+ const instTracer = machine.instrumentation?.transition;
934
+ const tracer = instTracer ?? (style !== 'production' ? createTransitionTracer(hsm) : undefined);
812
935
  await executeTransitionRoutine(hsm, hsm._instance, this.plan, srcState, dstState, {
813
936
  style,
814
- ...(style !== 'production' ? { tracer: createTransitionTracer(hsm) } : {}),
937
+ ...(tracer !== undefined ? { tracer } : {}),
938
+ // The instrumentation seam's entry/exit spans are structural (not TraceLevel-gated); the
939
+ // console tracer keeps its verbose-only gating.
940
+ hookEvents: instTracer !== undefined,
815
941
  setCurrentState: state => {
816
942
  hsm.currentState = state;
817
943
  },
@@ -1405,42 +1531,60 @@ function createInitTask(host, resolver) {
1405
1531
  .then(() => executePendingTransition(host, resolver))
1406
1532
  .then(() => done())
1407
1533
  .catch((err) => {
1408
- host.dispatchErrorCallback(host, asError(err));
1534
+ host.reportDispatchError(asError(err));
1409
1535
  done();
1410
1536
  });
1411
1537
  };
1412
1538
  }
1539
+ /**
1540
+ * Run a dispatch body inside the ambient `dispatchContext` token so that any `notify`/`call`/timer
1541
+ * the handler issues can be attributed to the running `(macrostepId, stepSeq)` (CORE-B, §5.6.3).
1542
+ * Falls back to a plain run when no ALS is available (browser) or the host predates the seam.
1543
+ */
1544
+ function runWithinDispatch(host, run) {
1545
+ const machine = host;
1546
+ const wants = machine.needsDispatchContext?.() ?? host.traceLevel !== TraceLevel.PRODUCTION;
1547
+ if (!wants) {
1548
+ run();
1549
+ return;
1550
+ }
1551
+ const storage = exports.dispatchContext.get();
1552
+ if (storage === undefined) {
1553
+ run();
1554
+ return;
1555
+ }
1556
+ const token = machine.buildDispatchToken?.() ?? { machine: host };
1557
+ storage.run(token, run);
1558
+ }
1559
+ function currentPortCallToken() {
1560
+ const storage = portCallContext.get();
1561
+ return storage?.getStore();
1562
+ }
1413
1563
  /** @internal */
1414
1564
  function createNotificationTask(host, resolver, name, args) {
1415
1565
  const strategy = dispatchStrategyFor(host.traceLevel);
1416
1566
  return (done) => {
1417
- strategy
1418
- .dispatchEvent(host, resolver, name, ...args)
1419
- .catch((err) => host.dispatchErrorCallback(host, asError(err)))
1420
- .finally(() => done());
1567
+ runWithinDispatch(host, () => {
1568
+ void strategy
1569
+ .dispatchEvent(host, resolver, name, ...args)
1570
+ .catch((err) => host.reportDispatchError(asError(err)))
1571
+ .finally(() => done());
1572
+ });
1421
1573
  };
1422
1574
  }
1423
1575
  /** @internal */
1424
1576
  function createServiceTask(host, resolver, name, args, resolve, reject) {
1425
- const machine = host;
1426
1577
  return (done) => {
1427
1578
  const run = () => invokeHandler(host, resolver, name, args)
1428
1579
  .then(resolve)
1429
1580
  .catch((err) => {
1430
1581
  reject(asError(err));
1431
1582
  })
1432
- .catch((err) => host.dispatchErrorCallback(host, asError(err)))
1583
+ .catch((err) => host.reportDispatchError(asError(err)))
1433
1584
  .finally(() => done());
1434
- if (host.traceLevel === TraceLevel.PRODUCTION) {
1435
- void run();
1436
- return;
1437
- }
1438
- const storage = exports.dispatchContext.get();
1439
- if (storage === undefined) {
1585
+ runWithinDispatch(host, () => {
1440
1586
  void run();
1441
- return;
1442
- }
1443
- void storage.run({ machine }, run);
1587
+ });
1444
1588
  };
1445
1589
  }
1446
1590
  //#endregion
@@ -1451,6 +1595,9 @@ class HsmObject {
1451
1595
  topStateName;
1452
1596
  ctxTypeName;
1453
1597
  traceWriter;
1598
+ actorUuid;
1599
+ actorName;
1600
+ actorPath;
1454
1601
  /** @internal */
1455
1602
  _instance;
1456
1603
  /** @internal */
@@ -1465,7 +1612,9 @@ class HsmObject {
1465
1612
  dispatchErrorCallback;
1466
1613
  _traceLevel;
1467
1614
  _traceDomainStack;
1468
- constructor(TopState, instance, traceWriter, traceLevel, dispatchErrorCallback) {
1615
+ _instrumentationHost;
1616
+ _drainWaiters = [];
1617
+ constructor(TopState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity) {
1469
1618
  this._instance = instance;
1470
1619
  this._transitionState = undefined;
1471
1620
  this._traceLevel = traceLevel;
@@ -1475,6 +1624,9 @@ class HsmObject {
1475
1624
  this._jobs = [];
1476
1625
  this._hiPriorityJobs = [];
1477
1626
  this._isRunning = false;
1627
+ this.actorUuid = identity.uuid;
1628
+ this.actorName = identity.name;
1629
+ this.actorPath = identity.path;
1478
1630
  this.topState = TopState;
1479
1631
  this.topStateName = getStateName(TopState);
1480
1632
  this.ctxTypeName = Object.getPrototypeOf(instance.ctx).constructor.name;
@@ -1491,6 +1643,14 @@ class HsmObject {
1491
1643
  get port() {
1492
1644
  return this._instance.portRef;
1493
1645
  }
1646
+ /** The bound port when it provides a timer service, so service-call timeouts honour a virtual clock. */
1647
+ get callTimer() {
1648
+ const port = this._instance.portRef;
1649
+ if (port !== undefined && typeof port.setTimeout === 'function' && typeof port.clearTimeout === 'function') {
1650
+ return port;
1651
+ }
1652
+ return undefined;
1653
+ }
1494
1654
  get eventName() {
1495
1655
  return this._currentEventName ?? '';
1496
1656
  }
@@ -1538,14 +1698,37 @@ class HsmObject {
1538
1698
  set traceLevel(traceLevel) {
1539
1699
  this._traceLevel = traceLevel;
1540
1700
  }
1701
+ /**
1702
+ * Resolve when the actor next reaches stability (mailbox fully drained).
1703
+ *
1704
+ * The resolver fires at the queue-drain point — *after* {@link InstrumentationHost.onQueuesDrained}
1705
+ * — so the closing `macrostep.end` and the macrostep-boundary reset are observable before `sync()`
1706
+ * resolves, and a subsequent external stimulus deterministically starts its own macrostep. The
1707
+ * pushed task is a no-op (internal) whose only purpose is to guarantee a drain cycle occurs.
1708
+ */
1541
1709
  sync() {
1542
1710
  return new Promise(resolve => {
1543
- this.pushTask((doneCallback) => {
1544
- resolve();
1711
+ this._drainWaiters.push(resolve);
1712
+ const task = (doneCallback) => {
1545
1713
  doneCallback();
1546
- });
1714
+ };
1715
+ (0, instrumentation_1.setTaskMeta)(task, { internal: true });
1716
+ this.pushTask(task);
1547
1717
  });
1548
1718
  }
1719
+ /** Invoke the user dispatch-error callback, first notifying instrumentation (pure observer). */
1720
+ reportDispatchError(err) {
1721
+ this._instrumentationHost?.onDispatchError(err);
1722
+ this.dispatchErrorCallback(this, err);
1723
+ }
1724
+ flushDrainWaiters() {
1725
+ if (this._drainWaiters.length === 0)
1726
+ return;
1727
+ const waiters = this._drainWaiters;
1728
+ this._drainWaiters = [];
1729
+ for (const resolve of waiters)
1730
+ resolve();
1731
+ }
1549
1732
  pushTask(t) {
1550
1733
  this.enqueueTask(t, this._jobs);
1551
1734
  }
@@ -1573,6 +1756,8 @@ class HsmObject {
1573
1756
  dequeue() {
1574
1757
  if (this._hiPriorityJobs.length == 0 && this._jobs.length == 0) {
1575
1758
  this._isRunning = false;
1759
+ this._instrumentationHost?.onQueuesDrained();
1760
+ this.flushDrainWaiters();
1576
1761
  return;
1577
1762
  }
1578
1763
  const task = this._hiPriorityJobs.length > 0 ? this._hiPriorityJobs.shift() : this._jobs.shift();
@@ -1582,10 +1767,32 @@ class HsmObject {
1582
1767
  setTimeout(() => this.runTask(task).then(() => this.dequeue()), 0);
1583
1768
  }
1584
1769
  runTask(task) {
1770
+ this._instrumentationHost?.onTaskBegin(task);
1771
+ let outcome = 'ok';
1585
1772
  return new Promise(resolve => {
1586
- task(() => {
1587
- this.drainHiPriority().then(resolve);
1588
- });
1773
+ const runBody = () => {
1774
+ task(() => {
1775
+ this.drainHiPriority()
1776
+ .then(() => {
1777
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1778
+ resolve();
1779
+ })
1780
+ .catch((_err) => {
1781
+ outcome = 'error';
1782
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1783
+ resolve();
1784
+ });
1785
+ });
1786
+ };
1787
+ try {
1788
+ runBody();
1789
+ }
1790
+ catch (err) {
1791
+ outcome = 'error';
1792
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1793
+ resolve();
1794
+ throw asError(err);
1795
+ }
1589
1796
  });
1590
1797
  }
1591
1798
  drainHiPriority() {
@@ -1613,19 +1820,52 @@ class HsmObject {
1613
1820
  get traceHeader() {
1614
1821
  return `${this._traceDomainStack.length === 0 ? '' : this._traceDomainStack.join('|') + '|'}`;
1615
1822
  }
1823
+ get traceFrames() {
1824
+ return this._traceDomainStack.map(name => ({ name, kind: classifyFrameKind(name) }));
1825
+ }
1616
1826
  }
1617
1827
  exports.HsmObject = HsmObject;
1828
+ /** Best-effort classification of a live trace-domain name into a structured {@link TraceFrame.kind}. */
1829
+ function classifyFrameKind(name) {
1830
+ if (name.startsWith('#'))
1831
+ return 'event';
1832
+ if (name === 'execute')
1833
+ return 'handler';
1834
+ if (name === 'initialize' || name.startsWith('initialize'))
1835
+ return 'initialize';
1836
+ if (name.startsWith('transition'))
1837
+ return 'transition';
1838
+ if (name.includes('onEntry'))
1839
+ return 'onEntry';
1840
+ if (name.includes('onExit'))
1841
+ return 'onExit';
1842
+ return 'handler';
1843
+ }
1618
1844
  //#region machine
1619
1845
  class Machine extends HsmObject {
1620
1846
  transitionResolver;
1847
+ identity;
1848
+ instrumentation;
1621
1849
  _dispatchStrategy;
1622
1850
  protocolIndex;
1623
1851
  handlerFacade;
1624
1852
  selfActor;
1625
1853
  selfImmediate;
1626
1854
  actorFacades = new Map();
1627
- constructor(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, transitionResolver) {
1628
- super(topState, instance, traceWriter, traceLevel, dispatchErrorCallback);
1855
+ _macrostepCounter = 0;
1856
+ _currentMacrostep;
1857
+ _microstepFromState;
1858
+ _childSpawnCounters = new Map();
1859
+ _nextTraceCallId = 0;
1860
+ _proxiedPort;
1861
+ constructor(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, identity, instrumentation, transitionResolver) {
1862
+ super(topState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity);
1863
+ this.identity = identity;
1864
+ this.instrumentation = instrumentation;
1865
+ if (instrumentation !== undefined) {
1866
+ this._instrumentationHost = this;
1867
+ (0, instrumentation_1.notifyActorCreated)(instrumentation, identity);
1868
+ }
1629
1869
  Object.defineProperty(instance, types_1.kHandlerMachine, { value: this, enumerable: false, writable: false, configurable: false });
1630
1870
  this.protocolIndex = protocolIndex;
1631
1871
  cacheProtocolIndex(topState, protocolIndex);
@@ -1639,7 +1879,227 @@ class Machine extends HsmObject {
1639
1879
  Object.defineProperty(instance, 'notifyNow', { value: this.selfImmediate, enumerable: true, configurable: true });
1640
1880
  this.bindPort(instance.portRef);
1641
1881
  if (initialize) {
1642
- this.pushTask(createInitTask(this, this.transitionResolver));
1882
+ const initTask = createInitTask(this, this.transitionResolver);
1883
+ (0, instrumentation_1.setTaskMeta)(initTask, { event: 'initialize', queue: 'default', triggerKind: 'init', internal: false });
1884
+ this.pushTask(initTask);
1885
+ }
1886
+ }
1887
+ allocateChildSpawnIndex(childTopName) {
1888
+ const childName = (0, identity_1.actorNameFromTopState)(childTopName);
1889
+ const index = this._childSpawnCounters.get(childName) ?? 0;
1890
+ this._childSpawnCounters.set(childName, index + 1);
1891
+ return index;
1892
+ }
1893
+ needsDispatchContext() {
1894
+ return this.instrumentation !== undefined || this.traceLevel !== TraceLevel.PRODUCTION;
1895
+ }
1896
+ buildDispatchToken() {
1897
+ return {
1898
+ machine: this,
1899
+ actorUuid: this.actorUuid,
1900
+ macrostepId: this._currentMacrostep?.id,
1901
+ stepSeq: this._currentMacrostep?.stepSeq,
1902
+ };
1903
+ }
1904
+ readDispatchCause(kind) {
1905
+ const storage = exports.dispatchContext.get();
1906
+ const token = storage?.getStore();
1907
+ if (token?.actorUuid === undefined)
1908
+ return undefined;
1909
+ return {
1910
+ actorUuid: token.actorUuid,
1911
+ macrostepId: token.macrostepId,
1912
+ stepSeq: token.stepSeq,
1913
+ kind,
1914
+ };
1915
+ }
1916
+ nextTraceCallId() {
1917
+ this._nextTraceCallId += 1;
1918
+ return this._nextTraceCallId;
1919
+ }
1920
+ beginPortCall(method) {
1921
+ if (this.instrumentation === undefined)
1922
+ return undefined;
1923
+ const cause = this.readDispatchCause('wire');
1924
+ const info = {
1925
+ callId: this.nextTraceCallId(),
1926
+ method,
1927
+ cause,
1928
+ };
1929
+ (0, instrumentation_1.notifyPortCallBegin)(this.instrumentation, info);
1930
+ return info;
1931
+ }
1932
+ endPortCall(begin, outcome, error) {
1933
+ if (this.instrumentation === undefined || begin === undefined)
1934
+ return;
1935
+ const info = {
1936
+ callId: begin.callId,
1937
+ method: begin.method,
1938
+ outcome,
1939
+ error,
1940
+ };
1941
+ (0, instrumentation_1.notifyPortCallEnd)(this.instrumentation, info);
1942
+ }
1943
+ beginOutboundCall(service, targetUuid) {
1944
+ if (this.instrumentation === undefined)
1945
+ return undefined;
1946
+ const portToken = currentPortCallToken();
1947
+ const cause = this.readDispatchCause('wire') ?? portToken?.cause;
1948
+ const info = {
1949
+ callId: this.nextTraceCallId(),
1950
+ service,
1951
+ targetUuid,
1952
+ cause,
1953
+ };
1954
+ (0, instrumentation_1.notifyOutboundCallBegin)(this.instrumentation, info);
1955
+ return info;
1956
+ }
1957
+ endOutboundCall(begin, outcome, error) {
1958
+ if (this.instrumentation === undefined || begin === undefined)
1959
+ return;
1960
+ const info = {
1961
+ callId: begin.callId,
1962
+ service: begin.service,
1963
+ outcome,
1964
+ error,
1965
+ };
1966
+ (0, instrumentation_1.notifyOutboundCallEnd)(this.instrumentation, info);
1967
+ }
1968
+ slotBucket(eventName) {
1969
+ return this.protocolIndex.get(eventName)?.bucket ?? 'notifications';
1970
+ }
1971
+ onTaskBegin(task) {
1972
+ const meta = (0, instrumentation_1.getTaskMeta)(task);
1973
+ if (meta?.internal === true || this.instrumentation === undefined)
1974
+ return;
1975
+ if (this._currentMacrostep === undefined) {
1976
+ const id = `${this.actorUuid}:${++this._macrostepCounter}`;
1977
+ const trigger = meta?.event ?? 'unknown';
1978
+ const triggerKind = meta?.triggerKind ?? (this._jobs.length + this._hiPriorityJobs.length > 0 ? 'self' : 'external');
1979
+ this._currentMacrostep = {
1980
+ id,
1981
+ trigger,
1982
+ triggerKind,
1983
+ startState: this.currentStateName,
1984
+ stepSeq: -1,
1985
+ transitioned: false,
1986
+ outcome: 'ok',
1987
+ cause: meta?.cause,
1988
+ };
1989
+ (0, instrumentation_1.notifyMacrostepBegin)(this.instrumentation, {
1990
+ id,
1991
+ actor: this.identity,
1992
+ trigger,
1993
+ triggerKind,
1994
+ startState: this.currentStateName,
1995
+ cause: meta?.cause,
1996
+ delayMs: meta?.delayMs,
1997
+ });
1998
+ }
1999
+ const macrostep = this._currentMacrostep;
2000
+ macrostep.stepSeq += 1;
2001
+ const seq = macrostep.stepSeq;
2002
+ const fromState = this.currentStateName;
2003
+ this._microstepFromState = fromState;
2004
+ // Stamp seq + fromState on the task so onTaskEnd pairs correctly even when nested priority
2005
+ // drains mutate the shared macrostep counter between this task's begin and end.
2006
+ if (meta !== undefined) {
2007
+ meta.seq = seq;
2008
+ meta.fromState = fromState;
2009
+ }
2010
+ else {
2011
+ (0, instrumentation_1.setTaskMeta)(task, { seq, fromState });
2012
+ }
2013
+ const eventName = meta?.event ?? this.eventName ?? 'unknown';
2014
+ const handlerState = lookupHandlerState(this, eventName);
2015
+ const storage = exports.dispatchContext.get();
2016
+ const runMicrostep = () => {
2017
+ (0, instrumentation_1.notifyMicrostepBegin)(this.instrumentation, {
2018
+ macrostepId: macrostep.id,
2019
+ seq,
2020
+ event: eventName,
2021
+ bucket: this.slotBucket(eventName),
2022
+ queue: meta?.queue ?? 'default',
2023
+ fromState: this.currentStateName,
2024
+ handlerState,
2025
+ cause: meta?.cause,
2026
+ });
2027
+ };
2028
+ if (storage !== undefined && this.needsDispatchContext()) {
2029
+ storage.run(this.buildDispatchToken(), runMicrostep);
2030
+ }
2031
+ else {
2032
+ runMicrostep();
2033
+ }
2034
+ }
2035
+ onTaskEnd(task, outcome) {
2036
+ const meta = (0, instrumentation_1.getTaskMeta)(task);
2037
+ if (meta?.internal === true || this.instrumentation === undefined || this._currentMacrostep === undefined)
2038
+ return;
2039
+ const macrostep = this._currentMacrostep;
2040
+ const fromState = meta?.fromState ?? this._microstepFromState ?? macrostep.startState;
2041
+ const seq = meta?.seq ?? macrostep.stepSeq;
2042
+ const transitioned = this.currentStateName !== fromState;
2043
+ if (transitioned)
2044
+ macrostep.transitioned = true;
2045
+ if (outcome === 'error')
2046
+ macrostep.outcome = 'error';
2047
+ (0, instrumentation_1.notifyMicrostepEnd)(this.instrumentation, {
2048
+ macrostepId: macrostep.id,
2049
+ seq,
2050
+ toState: this.currentStateName,
2051
+ transitioned,
2052
+ async: false,
2053
+ outcome,
2054
+ });
2055
+ }
2056
+ onDispatchError(err) {
2057
+ if (this.instrumentation === undefined)
2058
+ return;
2059
+ (0, instrumentation_1.notifyError)(this.instrumentation, {
2060
+ phase: errorPhaseFromError(err),
2061
+ errorClass: err.name,
2062
+ error: err,
2063
+ recovered: false,
2064
+ });
2065
+ }
2066
+ onQueuesDrained() {
2067
+ if (this.instrumentation === undefined || this._currentMacrostep === undefined)
2068
+ return;
2069
+ const macrostep = this._currentMacrostep;
2070
+ (0, instrumentation_1.notifyMacrostepEnd)(this.instrumentation, {
2071
+ id: macrostep.id,
2072
+ endState: this.currentStateName,
2073
+ steps: macrostep.stepSeq + 1,
2074
+ transitioned: macrostep.transitioned,
2075
+ outcome: macrostep.outcome,
2076
+ });
2077
+ this._currentMacrostep = undefined;
2078
+ }
2079
+ resolveEnqueueCause() {
2080
+ const inherited = this.readDispatchCause('message');
2081
+ if (inherited === undefined) {
2082
+ return { actorUuid: this.actorUuid, kind: 'cause' };
2083
+ }
2084
+ if (inherited.actorUuid !== this.actorUuid) {
2085
+ return inherited;
2086
+ }
2087
+ return { ...inherited, kind: 'cause' };
2088
+ }
2089
+ enqueueWithInstrumentation(task, event, queue, triggerKind) {
2090
+ const cause = this.resolveEnqueueCause();
2091
+ (0, instrumentation_1.setTaskMeta)(task, { event, queue, cause, triggerKind });
2092
+ (0, instrumentation_1.notifyEnqueue)(this.instrumentation, {
2093
+ event,
2094
+ queue,
2095
+ cause,
2096
+ targetUuid: this.actorUuid,
2097
+ });
2098
+ if (queue === 'priority') {
2099
+ this.pushHiPriorityTask(task);
2100
+ }
2101
+ else {
2102
+ this.pushTask(task);
1643
2103
  }
1644
2104
  }
1645
2105
  get traceLevel() {
@@ -1659,12 +2119,22 @@ class Machine extends HsmObject {
1659
2119
  }
1660
2120
  this.recordObserverEvent(name, args);
1661
2121
  return new Promise((resolve, reject) => {
1662
- this.pushTask(createServiceTask(this, this.transitionResolver, name, args, resolve, reject));
2122
+ const task = createServiceTask(this, this.transitionResolver, name, args, resolve, reject);
2123
+ if (this.instrumentation !== undefined) {
2124
+ this.enqueueWithInstrumentation(task, name, 'default', 'call');
2125
+ return;
2126
+ }
2127
+ this.pushTask(task);
1663
2128
  });
1664
2129
  }
1665
2130
  dispatchNotification(name, args, queue) {
1666
2131
  this.recordObserverEvent(name, args);
1667
2132
  const task = createNotificationTask(this, this.transitionResolver, name, args);
2133
+ if (this.instrumentation !== undefined) {
2134
+ const triggerKind = this._currentMacrostep === undefined ? 'external' : 'self';
2135
+ this.enqueueWithInstrumentation(task, name, queue, triggerKind);
2136
+ return;
2137
+ }
1668
2138
  if (queue === 'priority') {
1669
2139
  this.pushHiPriorityTask(task);
1670
2140
  }
@@ -1681,14 +2151,60 @@ class Machine extends HsmObject {
1681
2151
  return facade;
1682
2152
  }
1683
2153
  scheduleNotification(ms, name, args) {
1684
- const enqueue = () => {
1685
- this.dispatchNotification(name, args, 'default');
1686
- };
1687
2154
  const port = this._instance.portRef;
1688
2155
  if (port === undefined) {
1689
2156
  throw new Error('ihsm: deferred notification requires a port');
1690
2157
  }
1691
- port.setTimeout(enqueue, ms);
2158
+ if (this.instrumentation === undefined) {
2159
+ port.setTimeout(() => this.dispatchNotification(name, args, 'default'), ms);
2160
+ return;
2161
+ }
2162
+ // Capture the arming step's dispatch token now (the timer fires later while the actor is idle,
2163
+ // so the ambient token is gone by then). The fired macrostep links back to it as `timer` (§5.3.1).
2164
+ const armed = this.readDispatchCause('timer');
2165
+ const cause = armed ?? { actorUuid: this.actorUuid, kind: 'timer' };
2166
+ port.setTimeout(() => this.enqueueTimerNotification(name, args, cause, ms), ms);
2167
+ }
2168
+ enqueueTimerNotification(name, args, cause, delayMs) {
2169
+ this.recordObserverEvent(name, args);
2170
+ const task = createNotificationTask(this, this.transitionResolver, name, args);
2171
+ (0, instrumentation_1.setTaskMeta)(task, { event: name, queue: 'default', cause, triggerKind: 'timer', delayMs });
2172
+ (0, instrumentation_1.notifyEnqueue)(this.instrumentation, { event: name, queue: 'default', cause, delayMs, targetUuid: this.actorUuid });
2173
+ this.pushTask(task);
2174
+ }
2175
+ _actorLogger;
2176
+ /** Severity-typed handler logger surfaced as `this.hsm.log.*` (CORE-F, §4.10.1). */
2177
+ get logger() {
2178
+ if (this._actorLogger === undefined) {
2179
+ const emit = (severity, message, attributes) => this.emitUserLog(severity, message, attributes);
2180
+ this._actorLogger = {
2181
+ trace: (m, a) => emit('trace', m, a),
2182
+ debug: (m, a) => emit('debug', m, a),
2183
+ info: (m, a) => emit('info', m, a),
2184
+ warn: (m, a) => emit('warn', m, a),
2185
+ error: (m, a) => emit('error', m, a),
2186
+ fatal: (m, a) => emit('fatal', m, a),
2187
+ };
2188
+ }
2189
+ return this._actorLogger;
2190
+ }
2191
+ emitUserLog(severity, message, attributes) {
2192
+ const isError = message instanceof Error;
2193
+ const text = isError ? message.message : message;
2194
+ const body = `${this.traceHeader}${this.currentStateName}: ${text}`;
2195
+ // User logs fire on intent and are never TraceLevel-gated; mirror to the TraceWriter for console.
2196
+ this.traceWriter.write(this, body);
2197
+ if (this.instrumentation === undefined)
2198
+ return;
2199
+ const record = {
2200
+ severity,
2201
+ body,
2202
+ attributes,
2203
+ frames: this.traceFrames,
2204
+ error: isError ? message : undefined,
2205
+ source: 'user',
2206
+ };
2207
+ (0, instrumentation_1.notifyLog)(this.instrumentation, record);
1692
2208
  }
1693
2209
  /** @internal Binds deferred self-notifications to a port instance. */
1694
2210
  bindPort(portRef) {
@@ -1704,7 +2220,50 @@ class Machine extends HsmObject {
1704
2220
  },
1705
2221
  transition: next => machine.transition(next),
1706
2222
  get port() {
1707
- return instance.portRef;
2223
+ const rawPort = instance.portRef;
2224
+ if (machine.instrumentation === undefined || rawPort === undefined || typeof rawPort !== 'object') {
2225
+ return rawPort;
2226
+ }
2227
+ if (machine._proxiedPort !== undefined)
2228
+ return machine._proxiedPort;
2229
+ const proxy = new Proxy(rawPort, {
2230
+ get(target, prop, receiver) {
2231
+ const value = Reflect.get(target, prop, receiver);
2232
+ if (typeof value !== 'function')
2233
+ return value;
2234
+ const method = String(prop);
2235
+ return (...args) => {
2236
+ const begin = machine.beginPortCall(method);
2237
+ if (begin === undefined) {
2238
+ return Reflect.apply(value, target, args);
2239
+ }
2240
+ const call = () => Reflect.apply(value, target, args);
2241
+ const storage = portCallContext.get();
2242
+ try {
2243
+ const result = storage !== undefined ? storage.run({ machine, callId: begin.callId, method, cause: begin.cause }, call) : call();
2244
+ if (result instanceof Promise) {
2245
+ return result.then((value) => {
2246
+ machine.endPortCall(begin, 'ok');
2247
+ return value;
2248
+ }, (cause) => {
2249
+ const err = asError(cause);
2250
+ machine.endPortCall(begin, 'error', err);
2251
+ throw err;
2252
+ });
2253
+ }
2254
+ machine.endPortCall(begin, 'ok');
2255
+ return result;
2256
+ }
2257
+ catch (cause) {
2258
+ const err = asError(cause);
2259
+ machine.endPortCall(begin, 'error', err);
2260
+ throw err;
2261
+ }
2262
+ };
2263
+ },
2264
+ });
2265
+ machine._proxiedPort = proxy;
2266
+ return proxy;
1708
2267
  },
1709
2268
  unhandled: () => machine.unhandled(),
1710
2269
  get eventName() {
@@ -1728,6 +2287,24 @@ class Machine extends HsmObject {
1728
2287
  get traceHeader() {
1729
2288
  return machine.traceHeader;
1730
2289
  },
2290
+ get traceFrames() {
2291
+ return machine.traceFrames;
2292
+ },
2293
+ get log() {
2294
+ return machine.logger;
2295
+ },
2296
+ get id() {
2297
+ return machine.actorUuid;
2298
+ },
2299
+ get actorUuid() {
2300
+ return machine.actorUuid;
2301
+ },
2302
+ get actorName() {
2303
+ return machine.actorName;
2304
+ },
2305
+ get actorPath() {
2306
+ return machine.actorPath;
2307
+ },
1731
2308
  get traceLevel() {
1732
2309
  return machine.traceLevel;
1733
2310
  },
@@ -1795,6 +2372,18 @@ class Machine extends HsmObject {
1795
2372
  get traceHeader() {
1796
2373
  return machine.traceHeader;
1797
2374
  },
2375
+ get id() {
2376
+ return machine.actorUuid;
2377
+ },
2378
+ get actorUuid() {
2379
+ return machine.actorUuid;
2380
+ },
2381
+ get actorName() {
2382
+ return machine.actorName;
2383
+ },
2384
+ get actorPath() {
2385
+ return machine.actorPath;
2386
+ },
1798
2387
  };
1799
2388
  if (includeState) {
1800
2389
  Object.defineProperties(facade, {
@@ -1865,9 +2454,16 @@ function defaultDispatchErrorCallback(hsm, err) {
1865
2454
  throw err;
1866
2455
  }
1867
2456
  /** @internal Spawn with embodiment kind — used by factories and `ihsm/testing`. */
1868
- function spawnActor(kind, topState, ctx, port, options) {
2457
+ function spawnActor(kind, topState, ctx, port, options, spawnContext = {}) {
1869
2458
  const { initialize = exports.defaultInitialize, traceLevel = TraceLevel.DEBUG, traceWriter = exports.defaultTraceWriter, dispatchErrorCallback = defaultDispatchErrorCallback, transitions } = options;
1870
2459
  const protocolIndex = buildProtocolIndex(topState);
2460
+ const topStateName = getStateName(topState);
2461
+ const parentMachine = spawnContext.parentMachine;
2462
+ // Tracing is a cross-cutting concern: the actor adopts the globally-registered collector(s) at
2463
+ // spawn (no per-actor `instrumentation` option). Snapshotting here keeps "no collector" actors
2464
+ // at zero overhead while still sharing one collector instance across a parent and its children.
2465
+ const resolvedInstrumentation = (0, instrumentation_1.getActiveInstrumentation)();
2466
+ const identity = parentMachine !== undefined ? (0, identity_1.mintActorIdentity)('child', (0, identity_1.childActorPath)(parentMachine.actorPath, topStateName, parentMachine.allocateChildSpawnIndex(topStateName)), parentMachine.actorUuid) : (0, identity_1.mintActorIdentity)(kind, (0, identity_1.rootActorPath)(topStateName));
1871
2467
  const boundPort = (port ?? new Port());
1872
2468
  const instance = {
1873
2469
  ctx,
@@ -1875,7 +2471,26 @@ function spawnActor(kind, topState, ctx, port, options) {
1875
2471
  portRef: boundPort,
1876
2472
  };
1877
2473
  Object.setPrototypeOf(instance, topState.prototype);
1878
- const machine = new Machine(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, transitions ?? new RuntimeTransitionResolver());
2474
+ const machine = new Machine(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, identity, resolvedInstrumentation, transitions ?? new RuntimeTransitionResolver());
2475
+ if (resolvedInstrumentation !== undefined) {
2476
+ const token = exports.dispatchContext.get()?.getStore();
2477
+ const parentCause = token?.actorUuid !== undefined
2478
+ ? {
2479
+ actorUuid: token.actorUuid,
2480
+ macrostepId: token.macrostepId,
2481
+ stepSeq: token.stepSeq,
2482
+ kind: 'spawn',
2483
+ }
2484
+ : {
2485
+ actorUuid: identity.parentUuid ?? identity.uuid,
2486
+ kind: 'spawn',
2487
+ };
2488
+ const spawnInfo = {
2489
+ parent: parentCause,
2490
+ child: identity,
2491
+ };
2492
+ (0, instrumentation_1.notifyActorSpawned)(resolvedInstrumentation, spawnInfo);
2493
+ }
1879
2494
  const portKind = isRequestingPort(boundPort) ? 'child' : kind === 'root' ? 'inbound' : kind;
1880
2495
  boundPort.actor = createActorHandle(machine, topState, protocolIndex, portKind);
1881
2496
  return createActorHandle(machine, topState, protocolIndex, kind);
@@ -1896,11 +2511,20 @@ function asParentActor(handler) {
1896
2511
  }
1897
2512
  /** Parent composes a child machine — returns full child protocol shell with `parent` set. */
1898
2513
  function makeChildActor(parent, childTop, childCtx, port, options = {}) {
1899
- const child = spawnActor('child', childTop, childCtx, port, options);
2514
+ const parentMachine = parent[types_1.kParentLink];
2515
+ const child = spawnActor('child', childTop, childCtx, port, options, { parentMachine: parentMachine });
1900
2516
  Object.defineProperty(child, 'parent', { value: parent, enumerable: true, writable: false, configurable: true });
1901
2517
  return child;
1902
2518
  }
1903
2519
  var types_2 = require("./types");
1904
2520
  Object.defineProperty(exports, "kHandlerMachine", { enumerable: true, get: function () { return types_2.kHandlerMachine; } });
1905
2521
  Object.defineProperty(exports, "kParentLink", { enumerable: true, get: function () { return types_2.kParentLink; } });
2522
+ var identity_2 = require("./identity");
2523
+ Object.defineProperty(exports, "configureRunSeed", { enumerable: true, get: function () { return identity_2.configureRunSeed; } });
2524
+ Object.defineProperty(exports, "getRunSeed", { enumerable: true, get: function () { return identity_2.getRunSeed; } });
2525
+ Object.defineProperty(exports, "getRunNamespace", { enumerable: true, get: function () { return identity_2.getRunNamespace; } });
2526
+ Object.defineProperty(exports, "actorNameFromTopState", { enumerable: true, get: function () { return identity_2.actorNameFromTopState; } });
2527
+ Object.defineProperty(exports, "mintActorIdentity", { enumerable: true, get: function () { return identity_2.mintActorIdentity; } });
2528
+ Object.defineProperty(exports, "rootActorPath", { enumerable: true, get: function () { return identity_2.rootActorPath; } });
2529
+ Object.defineProperty(exports, "childActorPath", { enumerable: true, get: function () { return identity_2.childActorPath; } });
1906
2530
  //# sourceMappingURL=runtime.js.map