libpetri 2.11.0 → 2.12.1

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.
@@ -1,6 +1,6 @@
1
1
  import { Writable } from 'node:stream';
2
- import { P as PetriNet, b as Transition, T as Token } from '../petri-net-B4wQwsUj.js';
3
- import { E as EventStore, N as NetEvent } from '../event-store-CAkN_ayv.js';
2
+ import { P as PetriNet, b as Transition, T as Token } from '../petri-net-Byc3gSgJ.js';
3
+ import { E as EventStore, N as NetEvent } from '../event-store--nMbAO1v.js';
4
4
 
5
5
  /**
6
6
  * Commands sent from debug UI client to server via WebSocket.
@@ -1,5 +1,5 @@
1
1
  import { Application } from 'typedoc';
2
- import { P as PetriNet, S as SubnetDef, o as Instance } from '../petri-net-B4wQwsUj.js';
2
+ import { P as PetriNet, S as SubnetDef, o as Instance } from '../petri-net-Byc3gSgJ.js';
3
3
 
4
4
  /**
5
5
  * TypeDoc plugin for `@petrinet` and `@subnet` tags — auto-generates
@@ -1,4 +1,4 @@
1
- import { T as Token } from './petri-net-B4wQwsUj.js';
1
+ import { T as Token } from './petri-net-Byc3gSgJ.js';
2
2
 
3
3
  /**
4
4
  * Events emitted during Petri Net execution.
@@ -1,4 +1,4 @@
1
- import { P as PetriNet } from '../petri-net-B4wQwsUj.js';
1
+ import { P as PetriNet } from '../petri-net-Byc3gSgJ.js';
2
2
 
3
3
  /**
4
4
  * Format-agnostic typed graph model.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { P as PetriNet, S as SubnetDef, a as Place, T as Token, b as Transition, E as EnvironmentPlace, c as TransitionContext, O as Out } from './petri-net-B4wQwsUj.js';
2
- export { A as Arc, d as ArcInhibitor, e as ArcInput, f as ArcOutput, g as ArcRead, h as ArcReset, C as Channel, i as ComposeBindings, F as FusionSet, j as FusionSetBuilder, I as In, k as InAll, l as InAtLeast, m as InExactly, n as InOne, o as Instance, p as Interface, q as InterfaceBuilder, K as KeyFn, L as LogFn, M as MAX_DURATION_MS, r as MatchKey, s as MatchSpec, N as NameId, t as OutAnd, u as OutForwardInput, v as OutPlace, w as OutTimeout, x as OutXor, y as OutputEntry, z as PetriNetBuilder, B as Port, D as PortDirection, G as SubnetDefBuilder, H as SubnetInstance, J as Timing, Q as TimingDeadline, R as TimingDelayed, U as TimingExact, V as TimingImmediate, W as TimingWindow, X as TokenInput, Y as TokenOutput, Z as TransitionAction, _ as TransitionBuilder, $ as VerificationHarness, a0 as VerificationResult, a1 as all, a2 as allPlaces, a3 as and, a4 as andPlaces, a5 as arcPlace, a6 as atLeast, a7 as consumptionCount, a8 as deadline, a9 as delayed, aa as earliest, ab as enumerateBranches, ac as environmentPlace, ad as exact, ae as exactly, af as fork, ag as forwardInput, ah as hasDeadline, ai as hasGuard, aj as immediate, ak as inhibitorArc, al as inputArc, am as isUnit, an as keyForPlace, ao as latest, ap as matchCorrelates, aq as matchKey, ar as matchSpec, as as matchesGuard, at as nameId, au as one, av as outPlace, aw as outputArc, ax as passthrough, ay as place, az as produce, aA as readArc, aB as requiredCount, aC as resetArc, aD as timeout, aE as timeoutPlace, aF as tokenAt, aG as tokenOf, aH as transform, aI as transformAsync, aJ as transformFrom, aK as unitToken, aL as window, aM as withTimeout, aN as xor, aO as xorPlaces } from './petri-net-B4wQwsUj.js';
3
- import { E as EventStore } from './event-store-CAkN_ayv.js';
4
- export { A as ActionTimedOut, a as ExecutionCompleted, b as ExecutionStarted, I as InMemoryEventStore, L as LogMessage, M as MarkingSnapshot, N as NetEvent, T as TokenAdded, c as TokenRemoved, d as TransitionClockRestarted, e as TransitionCompleted, f as TransitionEnabled, g as TransitionFailed, h as TransitionStarted, i as TransitionTimedOut, j as eventTransitionName, k as eventsOfType, l as failures, m as filterEvents, n as inMemoryEventStore, o as isFailureEvent, p as noopEventStore, t as transitionEvents } from './event-store-CAkN_ayv.js';
1
+ import { P as PetriNet, S as SubnetDef, a as Place, T as Token, b as Transition, E as EnvironmentPlace, c as TransitionContext, O as Out } from './petri-net-Byc3gSgJ.js';
2
+ export { A as Arc, d as ArcInhibitor, e as ArcInput, f as ArcOutput, g as ArcRead, h as ArcReset, C as Channel, i as ComposeBindings, F as FusionSet, j as FusionSetBuilder, I as In, k as InAll, l as InAtLeast, m as InExactly, n as InOne, o as Instance, p as Interface, q as InterfaceBuilder, K as KeyFn, L as LogFn, M as MAX_DURATION_MS, r as MatchKey, s as MatchSpec, N as NameId, t as OutAnd, u as OutForwardInput, v as OutPlace, w as OutTimeout, x as OutXor, y as OutputEntry, z as PetriNetBuilder, B as Port, D as PortDirection, G as SubnetDefBuilder, H as SubnetInstance, J as Timing, Q as TimingDeadline, R as TimingDelayed, U as TimingExact, V as TimingImmediate, W as TimingWindow, X as TokenInput, Y as TokenOutput, Z as TransitionAction, _ as TransitionBuilder, $ as VerificationHarness, a0 as VerificationResult, a1 as all, a2 as allPlaces, a3 as and, a4 as andPlaces, a5 as arcPlace, a6 as atLeast, a7 as consumptionCount, a8 as deadline, a9 as delayed, aa as earliest, ab as enumerateBranches, ac as environmentPlace, ad as exact, ae as exactly, af as fork, ag as forwardInput, ah as hasDeadline, ai as hasGuard, aj as immediate, ak as inhibitorArc, al as inputArc, am as isUnit, an as keyForPlace, ao as latest, ap as matchCorrelates, aq as matchKey, ar as matchSpec, as as matchesGuard, at as nameId, au as one, av as outPlace, aw as outputArc, ax as passthrough, ay as place, az as produce, aA as readArc, aB as requiredCount, aC as resetArc, aD as timeout, aE as timeoutPlace, aF as tokenAt, aG as tokenOf, aH as transform, aI as transformAsync, aJ as transformFrom, aK as unitToken, aL as window, aM as withTimeout, aN as xor, aO as xorPlaces } from './petri-net-Byc3gSgJ.js';
3
+ import { E as EventStore } from './event-store--nMbAO1v.js';
4
+ export { A as ActionTimedOut, a as ExecutionCompleted, b as ExecutionStarted, I as InMemoryEventStore, L as LogMessage, M as MarkingSnapshot, N as NetEvent, T as TokenAdded, c as TokenRemoved, d as TransitionClockRestarted, e as TransitionCompleted, f as TransitionEnabled, g as TransitionFailed, h as TransitionStarted, i as TransitionTimedOut, j as eventTransitionName, k as eventsOfType, l as failures, m as filterEvents, n as inMemoryEventStore, o as isFailureEvent, p as noopEventStore, t as transitionEvents } from './event-store--nMbAO1v.js';
5
5
 
6
6
  /**
7
7
  * Discriminated sum-type abstraction over Petri nets, distinguishing **closed**
@@ -344,6 +344,27 @@ declare class BitmapNetExecutor implements PetriNetExecutor {
344
344
  */
345
345
  private fireReadyImmediate;
346
346
  private fireReadyGeneral;
347
+ /**
348
+ * Fires a transition, containing any synchronous throw to that one firing so it fails
349
+ * only that transition, not the whole run.
350
+ *
351
+ * Without this boundary an unchecked throw raised while a transition fires — a hostile
352
+ * EventStore.append on a token-removed or transition-started emit, or an error thrown while
353
+ * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
354
+ * The transition is instead failed and marked dirty for re-evaluation, the same treatment an
355
+ * asynchronously-reported failure gets. Enablement-phase throws (a guard or key function that
356
+ * throws inside canEnable, before the firing) run outside this boundary and are not contained
357
+ * here.
358
+ *
359
+ * fireTransition removes tokens from the marking before it reconciles the presence
360
+ * bitmap, so a throw inside that window would leave bits asserting tokens that are gone;
361
+ * the recovery re-runs updateBitmapAfterConsumption against the real marking.
362
+ *
363
+ * Unlike the Java runtime there is no VirtualMachineError/LinkageError analogue on the JS
364
+ * side, so every throw is contained here — there is deliberately no fatal-rethrow escape
365
+ * hatch.
366
+ */
367
+ private fireTransitionContained;
347
368
  private fireTransition;
348
369
  private updateBitmapAfterConsumption;
349
370
  private processCompletedTransitions;
@@ -609,6 +630,28 @@ declare class PrecompiledNetExecutor implements PetriNetExecutor {
609
630
  */
610
631
  private fireReadyImmediate;
611
632
  private fireReadyGeneral;
633
+ /**
634
+ * Fires a transition, containing any synchronous throw to that one firing so it fails
635
+ * only that transition, not the whole run.
636
+ *
637
+ * Without this boundary an unchecked throw raised while a transition fires — a hostile
638
+ * EventStore.append on a token-removed or transition-started emit, or an error thrown while
639
+ * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
640
+ * The transition is instead failed and marked dirty for re-evaluation, the same treatment an
641
+ * asynchronously-reported failure gets. Enablement-phase throws (a guard or key function that
642
+ * throws inside canEnable, before the firing) run outside this boundary and are not contained
643
+ * here.
644
+ *
645
+ * fireTransition drains tokens from the queues before it reconciles the presence bitmap,
646
+ * so a throw inside that window would leave bits asserting tokens that are gone; the
647
+ * recovery re-runs updateBitmapAfterConsumption against the true queue lengths.
648
+ *
649
+ * Named to avoid colliding with the pre-existing {@link fireTransitionGuarded}
650
+ * guard-consume helper. Unlike the Java runtime there is no VirtualMachineError/
651
+ * LinkageError analogue on the JS side, so every throw is contained here — there is
652
+ * deliberately no fatal-rethrow escape hatch.
653
+ */
654
+ private fireTransitionContained;
612
655
  private fireTransition;
613
656
  /**
614
657
  * Consumes the name-matched tokens for a ν-net join (NU-020): correlated
@@ -677,6 +720,10 @@ declare function validateOutSpec(tName: string, spec: Out, producedPlaceNames: S
677
720
  * - **AND**: recurses into all children (all branches get tokens).
678
721
  * - **XOR**: disallowed — timeout cannot choose a branch non-deterministically.
679
722
  * - **Timeout**: disallowed — nested timeouts would create ambiguous recovery paths.
723
+ *
724
+ * Writes through {@link TransitionContext.outputToHarvest} rather than `output(...)`:
725
+ * by this point the context has been detached, so the ordinary write target is closed
726
+ * and only the harvest collector still reaches the marking.
680
727
  */
681
728
  declare function produceTimeoutOutput(context: TransitionContext, timeoutChild: Out): void;
682
729
 
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  timeoutPlace,
13
13
  xor,
14
14
  xorPlaces
15
- } from "./chunk-YKJQ7IKZ.js";
15
+ } from "./chunk-V3WTQRHC.js";
16
16
  import {
17
17
  eventTransitionName,
18
18
  isFailureEvent
@@ -213,12 +213,69 @@ function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
213
213
  };
214
214
  }
215
215
 
216
+ // src/core/token-output.ts
217
+ var TokenOutput = class {
218
+ _entries = [];
219
+ /**
220
+ * Once true, further writes are dropped instead of appended. Set by the executor
221
+ * (via {@link import('./transition-context.js').TransitionContext.detachForTimeout})
222
+ * when a firing times out, so the action it has stopped waiting for can no longer
223
+ * reach the marking. Irreversible.
224
+ */
225
+ detached = false;
226
+ /** Add a value to an output place (creates token with current timestamp). */
227
+ add(place2, value) {
228
+ if (this.detached) return this;
229
+ this._entries.push({ place: place2, token: tokenOf(value) });
230
+ return this;
231
+ }
232
+ /** Add a pre-existing token to an output place. */
233
+ addToken(place2, token) {
234
+ if (this.detached) return this;
235
+ this._entries.push({ place: place2, token });
236
+ return this;
237
+ }
238
+ /**
239
+ * @internal Severs this collector: subsequent {@link add}/{@link addToken} calls are
240
+ * dropped. Executor machinery invoked when a firing times out — never call from an action.
241
+ */
242
+ detach() {
243
+ this.detached = true;
244
+ }
245
+ /** Returns all collected outputs. */
246
+ entries() {
247
+ return this._entries;
248
+ }
249
+ /** Check if any outputs were produced. */
250
+ isEmpty() {
251
+ return this._entries.length === 0;
252
+ }
253
+ /** Returns the set of place names that received tokens. */
254
+ placesWithTokens() {
255
+ const result = /* @__PURE__ */ new Set();
256
+ for (const entry of this._entries) {
257
+ result.add(entry.place.name);
258
+ }
259
+ return result;
260
+ }
261
+ };
262
+
216
263
  // src/core/transition-context.ts
217
264
  var EMPTY_ALIAS = /* @__PURE__ */ new Map();
218
265
  var GLOBAL_FRESH_NAME_COUNTER = 0;
219
266
  var TransitionContext = class {
220
267
  rawInput;
268
+ /**
269
+ * What the executor harvests. Identical to {@link writeTarget} until
270
+ * {@link detachForTimeout} replaces it with a fresh collector the action cannot reach.
271
+ */
221
272
  _rawOutput;
273
+ /**
274
+ * What the action writes to. Never reassigned, so an action already inside
275
+ * `output(...)` cannot be redirected mid-write; severance happens by detaching this
276
+ * collector, not by swapping it.
277
+ */
278
+ writeTarget;
222
279
  allowedInputs;
223
280
  allowedReads;
224
281
  allowedOutputs;
@@ -234,6 +291,7 @@ var TransitionContext = class {
234
291
  this._transitionName = transitionName;
235
292
  this.rawInput = rawInput;
236
293
  this._rawOutput = rawOutput;
294
+ this.writeTarget = rawOutput;
237
295
  this._inputPlaces = inputPlaces;
238
296
  this._readPlaces = readPlaces;
239
297
  this._outputPlaces = outputPlaces;
@@ -340,7 +398,7 @@ var TransitionContext = class {
340
398
  const actual = this.resolve(place2);
341
399
  this.requireOutput(actual);
342
400
  for (const value of values) {
343
- this._rawOutput.add(actual, value);
401
+ this.writeTarget.add(actual, value);
344
402
  }
345
403
  return this;
346
404
  }
@@ -356,7 +414,7 @@ var TransitionContext = class {
356
414
  const actual = this.resolve(place2);
357
415
  this.requireOutput(actual);
358
416
  for (const token of tokens) {
359
- this._rawOutput.addToken(actual, token);
417
+ this.writeTarget.addToken(actual, token);
360
418
  }
361
419
  return this;
362
420
  }
@@ -371,6 +429,38 @@ var TransitionContext = class {
371
429
  );
372
430
  }
373
431
  }
432
+ /**
433
+ * Severs the action's output from the marking, for use when a firing times out.
434
+ *
435
+ * After this call the action's `output(...)` writes are dropped, and the executor
436
+ * harvests a fresh collector the action holds no reference to. Anything the action
437
+ * wrote *before* the timeout is discarded with it: a partial result merged with the
438
+ * timeout branch would violate the transition's own output spec.
439
+ *
440
+ * This is the only isolation available — libpetri does not own the promise the action
441
+ * runs on, so it cannot stop the work, only stop the result from landing.
442
+ *
443
+ * @internal Executor machinery — must be called by the executor, never by an action.
444
+ */
445
+ detachForTimeout() {
446
+ this.writeTarget.detach();
447
+ this._rawOutput = new TokenOutput();
448
+ }
449
+ /**
450
+ * Produces into the executor's harvest collector rather than the action's write target.
451
+ *
452
+ * Identical to {@link output} before {@link detachForTimeout}; after it, this is the
453
+ * only route that still reaches the marking. Used by the executor to deposit the
454
+ * timeout branch.
455
+ *
456
+ * @internal Executor machinery — must be called by the executor, never by an action.
457
+ */
458
+ outputToHarvest(place2, value) {
459
+ const actual = this.resolve(place2);
460
+ this.requireOutput(actual);
461
+ this._rawOutput.add(actual, value);
462
+ return this;
463
+ }
374
464
  // ==================== ν-name minting (NU-010) ====================
375
465
  /**
376
466
  * @internal Installs the ν-name minter. Wired by the executor at firing time
@@ -462,37 +552,6 @@ var TokenInput = class {
462
552
  }
463
553
  };
464
554
 
465
- // src/core/token-output.ts
466
- var TokenOutput = class {
467
- _entries = [];
468
- /** Add a value to an output place (creates token with current timestamp). */
469
- add(place2, value) {
470
- this._entries.push({ place: place2, token: tokenOf(value) });
471
- return this;
472
- }
473
- /** Add a pre-existing token to an output place. */
474
- addToken(place2, token) {
475
- this._entries.push({ place: place2, token });
476
- return this;
477
- }
478
- /** Returns all collected outputs. */
479
- entries() {
480
- return this._entries;
481
- }
482
- /** Check if any outputs were produced. */
483
- isEmpty() {
484
- return this._entries.length === 0;
485
- }
486
- /** Returns the set of place names that received tokens. */
487
- placesWithTokens() {
488
- const result = /* @__PURE__ */ new Set();
489
- for (const entry of this._entries) {
490
- result.add(entry.place.name);
491
- }
492
- return result;
493
- }
494
- };
495
-
496
555
  // src/core/transition.ts
497
556
  var TRANSITION_KEY = /* @__PURE__ */ Symbol("Transition.internal");
498
557
  var EMPTY_PLACE_ALIAS = /* @__PURE__ */ new Map();
@@ -3006,6 +3065,25 @@ var OutViolationError = class extends Error {
3006
3065
  };
3007
3066
 
3008
3067
  // src/runtime/executor-support.ts
3068
+ function executeAction(t, context) {
3069
+ try {
3070
+ const stage = t.action(context);
3071
+ if (stage == null || typeof stage.then !== "function") {
3072
+ return Promise.reject(new Error(
3073
+ `'${t.name}': action returned null/non-thenable instead of a Promise`
3074
+ ));
3075
+ }
3076
+ return Promise.resolve(stage);
3077
+ } catch (err) {
3078
+ return Promise.reject(err);
3079
+ }
3080
+ }
3081
+ function swallowEventStoreFailure(when, err) {
3082
+ console.warn(
3083
+ `libpetri: EventStore.append threw while emitting ${when}; the event is dropped`,
3084
+ err
3085
+ );
3086
+ }
3009
3087
  var DEADLINE_TOLERANCE_MS = 5;
3010
3088
  function validateOutSpec(tName, spec, producedPlaceNames) {
3011
3089
  switch (spec.type) {
@@ -3047,11 +3125,11 @@ function validateOutSpec(tName, spec, producedPlaceNames) {
3047
3125
  function produceTimeoutOutput(context, timeoutChild) {
3048
3126
  switch (timeoutChild.type) {
3049
3127
  case "place":
3050
- context.output(timeoutChild.place, null);
3128
+ context.outputToHarvest(timeoutChild.place, null);
3051
3129
  break;
3052
3130
  case "forward-input": {
3053
3131
  const value = context.input(timeoutChild.from);
3054
- context.output(timeoutChild.to, value);
3132
+ context.outputToHarvest(timeoutChild.to, value);
3055
3133
  break;
3056
3134
  }
3057
3135
  case "and":
@@ -3506,7 +3584,7 @@ var BitmapNetExecutor = class {
3506
3584
  for (let tid = 0; tid < this.compiled.transitionCount; tid++) {
3507
3585
  if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
3508
3586
  if (this.canEnable(tid, this.markedPlaces)) {
3509
- this.fireTransition(tid);
3587
+ this.fireTransitionContained(tid);
3510
3588
  } else {
3511
3589
  this.enabledFlags[tid] = 0;
3512
3590
  this.enabledTransitionCount--;
@@ -3538,7 +3616,7 @@ var BitmapNetExecutor = class {
3538
3616
  for (const entry of ready) {
3539
3617
  const { tid } = entry;
3540
3618
  if (this.enabledFlags[tid] && this.canEnable(tid, freshSnap)) {
3541
- this.fireTransition(tid);
3619
+ this.fireTransitionContained(tid);
3542
3620
  freshSnap.set(this.markedPlaces);
3543
3621
  } else {
3544
3622
  this.enabledFlags[tid] = 0;
@@ -3547,6 +3625,53 @@ var BitmapNetExecutor = class {
3547
3625
  }
3548
3626
  }
3549
3627
  }
3628
+ /**
3629
+ * Fires a transition, containing any synchronous throw to that one firing so it fails
3630
+ * only that transition, not the whole run.
3631
+ *
3632
+ * Without this boundary an unchecked throw raised while a transition fires — a hostile
3633
+ * EventStore.append on a token-removed or transition-started emit, or an error thrown while
3634
+ * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
3635
+ * The transition is instead failed and marked dirty for re-evaluation, the same treatment an
3636
+ * asynchronously-reported failure gets. Enablement-phase throws (a guard or key function that
3637
+ * throws inside canEnable, before the firing) run outside this boundary and are not contained
3638
+ * here.
3639
+ *
3640
+ * fireTransition removes tokens from the marking before it reconciles the presence
3641
+ * bitmap, so a throw inside that window would leave bits asserting tokens that are gone;
3642
+ * the recovery re-runs updateBitmapAfterConsumption against the real marking.
3643
+ *
3644
+ * Unlike the Java runtime there is no VirtualMachineError/LinkageError analogue on the JS
3645
+ * side, so every throw is contained here — there is deliberately no fatal-rethrow escape
3646
+ * hatch.
3647
+ */
3648
+ fireTransitionContained(tid) {
3649
+ try {
3650
+ this.fireTransition(tid);
3651
+ } catch (e) {
3652
+ const t = this.compiled.transition(tid);
3653
+ if (this.enabledFlags[tid]) {
3654
+ this.enabledFlags[tid] = 0;
3655
+ this.enabledTransitionCount--;
3656
+ this.enabledAtMs[tid] = -Infinity;
3657
+ }
3658
+ if (this.inFlight.delete(t)) {
3659
+ this.inFlightFlags[tid] = 0;
3660
+ }
3661
+ this.updateBitmapAfterConsumption(tid);
3662
+ this.matchCaches[tid] = null;
3663
+ const err = e instanceof Error ? e : new Error(String(e));
3664
+ this.emitEvent({
3665
+ type: "transition-failed",
3666
+ timestamp: Date.now(),
3667
+ transitionName: t.name,
3668
+ errorMessage: err.message,
3669
+ exceptionType: err.name,
3670
+ stack: err.stack
3671
+ });
3672
+ this.markTransitionDirty(tid);
3673
+ }
3674
+ }
3550
3675
  fireTransition(tid) {
3551
3676
  const t = this.compiled.transition(tid);
3552
3677
  const inputs = new TokenInput();
@@ -3645,7 +3770,7 @@ var BitmapNetExecutor = class {
3645
3770
  );
3646
3771
  const freshNameBase = t.name;
3647
3772
  context.setFreshNameSupplier(() => nameId(`${freshNameBase}#${this.freshNameCounter++}`));
3648
- let actionPromise = t.action(context);
3773
+ let actionPromise = executeAction(t, context);
3649
3774
  if (t.hasActionTimeout()) {
3650
3775
  const timeoutSpec = t.actionTimeout;
3651
3776
  if (timeoutSpec === null) throw new Error(`Expected actionTimeout on ${t.name}`);
@@ -3657,6 +3782,7 @@ var BitmapNetExecutor = class {
3657
3782
  )
3658
3783
  ]).catch((err) => {
3659
3784
  if (err instanceof TimeoutSentinel) {
3785
+ context.detachForTimeout();
3660
3786
  produceTimeoutOutput(context, timeoutSpec.child);
3661
3787
  this.emitEvent({
3662
3788
  type: "action-timed-out",
@@ -3927,7 +4053,11 @@ var BitmapNetExecutor = class {
3927
4053
  // ======================== Event Emission ========================
3928
4054
  emitEvent(event) {
3929
4055
  if (this.eventStoreEnabled) {
3930
- this.eventStore.append(event);
4056
+ try {
4057
+ this.eventStore.append(event);
4058
+ } catch (err) {
4059
+ swallowEventStoreFailure(event.type, err);
4060
+ }
3931
4061
  }
3932
4062
  }
3933
4063
  };
@@ -4764,7 +4894,7 @@ var PrecompiledNetExecutor = class {
4764
4894
  for (let tid = 0; tid < tc; tid++) {
4765
4895
  if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
4766
4896
  if (this.canEnable(tid, this.markingBitmap)) {
4767
- this.fireTransition(tid);
4897
+ this.fireTransitionContained(tid);
4768
4898
  } else {
4769
4899
  this.enabledFlags[tid] = 0;
4770
4900
  this.enabledTransitionCount--;
@@ -4795,7 +4925,7 @@ var PrecompiledNetExecutor = class {
4795
4925
  for (const entry of ready) {
4796
4926
  const { tid } = entry;
4797
4927
  if (this.enabledFlags[tid] && this.canEnable(tid, freshSnap)) {
4798
- this.fireTransition(tid);
4928
+ this.fireTransitionContained(tid);
4799
4929
  freshSnap.set(this.markingBitmap);
4800
4930
  } else {
4801
4931
  this.enabledFlags[tid] = 0;
@@ -4804,6 +4934,60 @@ var PrecompiledNetExecutor = class {
4804
4934
  }
4805
4935
  }
4806
4936
  }
4937
+ /**
4938
+ * Fires a transition, containing any synchronous throw to that one firing so it fails
4939
+ * only that transition, not the whole run.
4940
+ *
4941
+ * Without this boundary an unchecked throw raised while a transition fires — a hostile
4942
+ * EventStore.append on a token-removed or transition-started emit, or an error thrown while
4943
+ * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
4944
+ * The transition is instead failed and marked dirty for re-evaluation, the same treatment an
4945
+ * asynchronously-reported failure gets. Enablement-phase throws (a guard or key function that
4946
+ * throws inside canEnable, before the firing) run outside this boundary and are not contained
4947
+ * here.
4948
+ *
4949
+ * fireTransition drains tokens from the queues before it reconciles the presence bitmap,
4950
+ * so a throw inside that window would leave bits asserting tokens that are gone; the
4951
+ * recovery re-runs updateBitmapAfterConsumption against the true queue lengths.
4952
+ *
4953
+ * Named to avoid colliding with the pre-existing {@link fireTransitionGuarded}
4954
+ * guard-consume helper. Unlike the Java runtime there is no VirtualMachineError/
4955
+ * LinkageError analogue on the JS side, so every throw is contained here — there is
4956
+ * deliberately no fatal-rethrow escape hatch.
4957
+ */
4958
+ fireTransitionContained(tid) {
4959
+ try {
4960
+ this.fireTransition(tid);
4961
+ } catch (e) {
4962
+ const t = this.program.compiled.transition(tid);
4963
+ if (this.enabledFlags[tid]) {
4964
+ this.enabledFlags[tid] = 0;
4965
+ this.enabledTransitionCount--;
4966
+ this.enabledAtMs[tid] = -Infinity;
4967
+ }
4968
+ if (this.inFlightFlags[tid]) {
4969
+ this.inFlightPromises[tid] = null;
4970
+ this.inFlightContexts[tid] = null;
4971
+ this.inFlightConsumed[tid] = null;
4972
+ this.inFlightResolves[tid] = null;
4973
+ this.inFlightErrors[tid] = null;
4974
+ this.inFlightFlags[tid] = 0;
4975
+ this.inFlightCount--;
4976
+ }
4977
+ this.updateBitmapAfterConsumption(tid);
4978
+ this.matchCaches[tid] = null;
4979
+ const err = e instanceof Error ? e : new Error(String(e));
4980
+ this.emitEvent({
4981
+ type: "transition-failed",
4982
+ timestamp: Date.now(),
4983
+ transitionName: t.name,
4984
+ errorMessage: err.message,
4985
+ exceptionType: err.name,
4986
+ stack: err.stack
4987
+ });
4988
+ this.markTransitionDirty(tid);
4989
+ }
4990
+ }
4807
4991
  fireTransition(tid) {
4808
4992
  const prog = this.program;
4809
4993
  const t = prog.compiled.transition(tid);
@@ -4952,7 +5136,7 @@ var PrecompiledNetExecutor = class {
4952
5136
  this.freshNameSuppliers[tid] = freshNameSupplier;
4953
5137
  }
4954
5138
  context.setFreshNameSupplier(freshNameSupplier);
4955
- let actionPromise = t.action(context);
5139
+ let actionPromise = executeAction(t, context);
4956
5140
  if (t.hasActionTimeout()) {
4957
5141
  const timeoutSpec = t.actionTimeout;
4958
5142
  if (timeoutSpec === null) throw new Error(`Expected actionTimeout on ${t.name}`);
@@ -4964,6 +5148,7 @@ var PrecompiledNetExecutor = class {
4964
5148
  )
4965
5149
  ]).catch((err) => {
4966
5150
  if (err instanceof TimeoutSentinel2) {
5151
+ context.detachForTimeout();
4967
5152
  produceTimeoutOutput(context, timeoutSpec.child);
4968
5153
  this.emitEvent({
4969
5154
  type: "action-timed-out",
@@ -5358,7 +5543,11 @@ var PrecompiledNetExecutor = class {
5358
5543
  // ======================== Event Emission ========================
5359
5544
  emitEvent(event) {
5360
5545
  if (this.eventStoreEnabled) {
5361
- this.eventStore.append(event);
5546
+ try {
5547
+ this.eventStore.append(event);
5548
+ } catch (err) {
5549
+ swallowEventStoreFailure(event.type, err);
5550
+ }
5362
5551
  }
5363
5552
  }
5364
5553
  };