libpetri 2.4.0 → 2.6.0

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.
@@ -0,0 +1,8 @@
1
+ import {
2
+ dotExport
3
+ } from "./chunk-ULX3OG6H.js";
4
+ import "./chunk-ATT7U5H5.js";
5
+ export {
6
+ dotExport
7
+ };
8
+ //# sourceMappingURL=dot-exporter-TYC6FUAD.js.map
@@ -1,4 +1,4 @@
1
- import { T as Token } from './petri-net-RpZxG0Io.js';
1
+ import { T as Token } from './petri-net-C3asscb4.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-RpZxG0Io.js';
1
+ import { P as PetriNet } from '../petri-net-C3asscb4.js';
2
2
 
3
3
  /**
4
4
  * Format-agnostic typed graph model.
@@ -133,12 +133,32 @@ declare function edgeStyle(arcType: EdgeCategory): EdgeVisual;
133
133
  * @module export/petri-net-mapper
134
134
  */
135
135
 
136
+ /**
137
+ * Selects how DOT export groups nodes into `subgraph cluster_*` blocks (per
138
+ * **MOD-040** / **EXP-016**).
139
+ *
140
+ * - `'auto'` — use subnet-membership metadata (per MOD-026) when the net
141
+ * carries any; otherwise fall back to instance-prefix name detection.
142
+ * Default.
143
+ * - `'metadata'` — strictly cluster from subnet-membership metadata; a node
144
+ * with no metadata entry — including a prefix-named instance node — is not
145
+ * clustered. Use `'auto'` to also cluster prefix-named nodes.
146
+ * - `'prefix'` — always cluster from instance-prefix name segments; ignore
147
+ * metadata.
148
+ * - `'none'` — emit no clusters; every node renders at the top level.
149
+ */
150
+ type ClusterSource = 'auto' | 'metadata' | 'prefix' | 'none';
136
151
  interface DotConfig {
137
152
  readonly direction: RankDir;
138
153
  readonly showTypes: boolean;
139
154
  readonly showIntervals: boolean;
140
155
  readonly showPriority: boolean;
141
156
  readonly environmentPlaces?: ReadonlySet<string>;
157
+ /**
158
+ * How to group nodes into `subgraph cluster_*` blocks (per MOD-040 /
159
+ * EXP-016). Additive; defaults to `'auto'` when omitted.
160
+ */
161
+ readonly clusterSource?: ClusterSource;
142
162
  }
143
163
  declare const DEFAULT_DOT_CONFIG: DotConfig;
144
164
  /** Sanitizes a name for use as a graph node ID. */
@@ -173,4 +193,4 @@ declare function renderDot(graph: Graph): string;
173
193
  */
174
194
  declare function dotExport(net: PetriNet, config?: DotConfig): string;
175
195
 
176
- export { type ArrowHead, DEFAULT_DOT_CONFIG, type DotConfig, type EdgeCategory, type EdgeLineStyle, type EdgeVisual, FONT, GRAPH, type Graph, type GraphEdge, type GraphNode, type NodeCategory, type NodeShape, type NodeVisual, type RankDir, type Subgraph, dotExport, edgeStyle, mapToGraph, nodeStyle, renderDot, sanitize };
196
+ export { type ArrowHead, type ClusterSource, DEFAULT_DOT_CONFIG, type DotConfig, type EdgeCategory, type EdgeLineStyle, type EdgeVisual, FONT, GRAPH, type Graph, type GraphEdge, type GraphNode, type NodeCategory, type NodeShape, type NodeVisual, type RankDir, type Subgraph, dotExport, edgeStyle, mapToGraph, nodeStyle, renderDot, sanitize };
@@ -8,8 +8,8 @@ import {
8
8
  nodeStyle,
9
9
  renderDot,
10
10
  sanitize
11
- } from "../chunk-B2CV5M3L.js";
12
- import "../chunk-FN773SSE.js";
11
+ } from "../chunk-ULX3OG6H.js";
12
+ import "../chunk-ATT7U5H5.js";
13
13
  export {
14
14
  DEFAULT_DOT_CONFIG,
15
15
  FONT,
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-RpZxG0Io.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, L as LogFn, M as MAX_DURATION_MS, r as OutAnd, s as OutForwardInput, t as OutPlace, u as OutTimeout, v as OutXor, w as OutputEntry, x as PetriNetBuilder, y as Port, z as PortDirection, B as SubnetDefBuilder, D as SubnetInstance, G as Timing, H as TimingDeadline, J as TimingDelayed, K as TimingExact, N as TimingImmediate, Q as TimingWindow, R as TokenInput, U as TokenOutput, V as TransitionAction, W as TransitionBuilder, X as VerificationHarness, Y as VerificationResult, Z as all, _ as allPlaces, $ as and, a0 as andPlaces, a1 as arcPlace, a2 as atLeast, a3 as consumptionCount, a4 as deadline, a5 as delayed, a6 as earliest, a7 as enumerateBranches, a8 as environmentPlace, a9 as exact, aa as exactly, ab as fork, ac as forwardInput, ad as hasDeadline, ae as hasGuard, af as immediate, ag as inhibitorArc, ah as inputArc, ai as isUnit, aj as latest, ak as matchesGuard, al as one, am as outPlace, an as outputArc, ao as passthrough, ap as place, aq as produce, ar as readArc, as as requiredCount, at as resetArc, au as timeout, av as timeoutPlace, aw as tokenAt, ax as tokenOf, ay as transform, az as transformAsync, aA as transformFrom, aB as unitToken, aC as window, aD as withTimeout, aE as xor, aF as xorPlaces } from './petri-net-RpZxG0Io.js';
3
- import { E as EventStore } from './event-store-DqXOj4j-.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-DqXOj4j-.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-C3asscb4.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, L as LogFn, M as MAX_DURATION_MS, r as OutAnd, s as OutForwardInput, t as OutPlace, u as OutTimeout, v as OutXor, w as OutputEntry, x as PetriNetBuilder, y as Port, z as PortDirection, B as SubnetDefBuilder, D as SubnetInstance, G as Timing, H as TimingDeadline, J as TimingDelayed, K as TimingExact, N as TimingImmediate, Q as TimingWindow, R as TokenInput, U as TokenOutput, V as TransitionAction, W as TransitionBuilder, X as VerificationHarness, Y as VerificationResult, Z as all, _ as allPlaces, $ as and, a0 as andPlaces, a1 as arcPlace, a2 as atLeast, a3 as consumptionCount, a4 as deadline, a5 as delayed, a6 as earliest, a7 as enumerateBranches, a8 as environmentPlace, a9 as exact, aa as exactly, ab as fork, ac as forwardInput, ad as hasDeadline, ae as hasGuard, af as immediate, ag as inhibitorArc, ah as inputArc, ai as isUnit, aj as latest, ak as matchesGuard, al as one, am as outPlace, an as outputArc, ao as passthrough, ap as place, aq as produce, ar as readArc, as as requiredCount, at as resetArc, au as timeout, av as timeoutPlace, aw as tokenAt, ax as tokenOf, ay as transform, az as transformAsync, aA as transformFrom, aB as unitToken, aC as window, aD as withTimeout, aE as xor, aF as xorPlaces } from './petri-net-C3asscb4.js';
3
+ import { E as EventStore } from './event-store-E_ahsEc9.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-E_ahsEc9.js';
5
5
 
6
6
  /**
7
7
  * Discriminated sum-type abstraction over Petri nets, distinguishing **closed**
@@ -221,6 +221,13 @@ interface BitmapNetExecutorOptions {
221
221
  environmentPlaces?: Set<EnvironmentPlace<any>>;
222
222
  /** Provides execution context data for each transition firing. */
223
223
  executionContextProvider?: (transitionName: string, consumed: Token<any>[]) => Map<string, unknown>;
224
+ /**
225
+ * Grace band (ms) beyond a hard deadline (`deadline()` / `window()`) before a transition is
226
+ * force-disabled with a `transition-timed-out` event (TIME-013). Defaults to {@link DEADLINE_TOLERANCE_MS}
227
+ * (5ms); `0` gives strict enforcement. Must be non-negative. Does not affect `exact()` transitions,
228
+ * which are enforced softly (TIME-006).
229
+ */
230
+ deadlineToleranceMs?: number;
224
231
  }
225
232
  /**
226
233
  * Async bitmap-based executor for Coloured Time Petri Nets.
@@ -262,6 +269,10 @@ declare class BitmapNetExecutor implements PetriNetExecutor {
262
269
  private readonly enabledFlags;
263
270
  /** Precomputed: 1 if transition has a finite deadline, 0 otherwise. */
264
271
  private readonly hasDeadlineFlags;
272
+ /** Precomputed: 1 for exact() transitions — enforced softly, never force-disabled (TIME-006). */
273
+ private readonly isExactFlags;
274
+ /** Grace band (ms) before a hard deadline force-disables (TIME-013). */
275
+ private readonly deadlineToleranceMs;
265
276
  private enabledTransitionCount;
266
277
  private readonly inFlight;
267
278
  private readonly inFlightPromises;
@@ -394,6 +405,12 @@ declare class PrecompiledNet {
394
405
  readonly earliestMs: Float64Array;
395
406
  readonly latestMs: Float64Array;
396
407
  readonly hasDeadline: Uint8Array;
408
+ /**
409
+ * 1 for `exact()` transitions. Exact transitions fire at the first opportunity at/after their
410
+ * target time (delayed-style liveness) and are never force-disabled by deadline enforcement —
411
+ * their upper bound is observed, not enforced. See TIME-006.
412
+ */
413
+ readonly isExact: Uint8Array;
397
414
  readonly priorities: Int32Array;
398
415
  readonly transitionToPriorityIndex: Uint32Array;
399
416
  readonly priorityLevels: readonly number[];
@@ -456,6 +473,13 @@ interface PrecompiledNetExecutorOptions {
456
473
  skipOutputValidation?: boolean;
457
474
  /** Reuse a precompiled program (avoids recompilation). */
458
475
  program?: PrecompiledNet;
476
+ /**
477
+ * Grace band (ms) beyond a hard deadline (`deadline()` / `window()`) before a transition is
478
+ * force-disabled with a `transition-timed-out` event (TIME-013). Defaults to {@link DEADLINE_TOLERANCE_MS}
479
+ * (5ms); `0` gives strict enforcement. Must be non-negative. Does not affect `exact()` transitions,
480
+ * which are enforced softly (TIME-006).
481
+ */
482
+ deadlineToleranceMs?: number;
459
483
  }
460
484
  /**
461
485
  * High-performance executor using `PrecompiledNet`.
@@ -470,6 +494,7 @@ declare class PrecompiledNetExecutor implements PetriNetExecutor {
470
494
  private readonly hasEnvironmentPlaces;
471
495
  private readonly executionContextProvider?;
472
496
  private readonly skipOutputValidation;
497
+ private readonly deadlineToleranceMs;
473
498
  private readonly startMs;
474
499
  private readonly eventStoreEnabled;
475
500
  /** Per-place token arrays, indexed by pid. */
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  timeoutPlace,
13
13
  xor,
14
14
  xorPlaces
15
- } from "./chunk-H62Z76FY.js";
15
+ } from "./chunk-ETNHEAS6.js";
16
16
  import {
17
17
  eventTransitionName,
18
18
  isFailureEvent
@@ -27,7 +27,7 @@ import {
27
27
  immediate,
28
28
  latest,
29
29
  window
30
- } from "./chunk-FN773SSE.js";
30
+ } from "./chunk-ATT7U5H5.js";
31
31
 
32
32
  // src/core/token.ts
33
33
  var UNIT_TOKEN = Object.freeze({
@@ -1646,12 +1646,25 @@ var PetriNet = class _PetriNet {
1646
1646
  name;
1647
1647
  places;
1648
1648
  transitions;
1649
+ /**
1650
+ * Subnet-membership metadata per **MOD-026**: maps each node name (place or
1651
+ * transition) contributed by exactly one directly-composed subnet to that
1652
+ * subnet's name. Shared places contributed by two or more subnets, and every
1653
+ * node of a net not built via {@link PetriNetBuilder.compose}`(SubnetDef)`,
1654
+ * are absent. Never null — an empty map when there is no metadata. The DOT
1655
+ * exporter renders `subgraph cluster_*` blocks from this map.
1656
+ *
1657
+ * V8 `Map` is insertion-ordered, preserving compose order so cluster
1658
+ * subgraphs render deterministically (cross-language byte-parity).
1659
+ */
1660
+ subnetMembership;
1649
1661
  /** @internal Use {@link PetriNet.builder} to create instances. */
1650
- constructor(key, name, places, transitions) {
1662
+ constructor(key, name, places, transitions, subnetMembership = /* @__PURE__ */ new Map()) {
1651
1663
  if (key !== PETRI_NET_KEY) throw new Error("Use PetriNet.builder() to create instances");
1652
1664
  this.name = name;
1653
1665
  this.places = places;
1654
1666
  this.transitions = transitions;
1667
+ this.subnetMembership = subnetMembership;
1655
1668
  }
1656
1669
  /**
1657
1670
  * Creates a new PetriNet with actions bound to transitions by name.
@@ -1676,17 +1689,28 @@ var PetriNet = class _PetriNet {
1676
1689
  boundTransitions.add(t);
1677
1690
  }
1678
1691
  }
1679
- return new _PetriNet(PETRI_NET_KEY, this.name, this.places, boundTransitions);
1692
+ return new _PetriNet(
1693
+ PETRI_NET_KEY,
1694
+ this.name,
1695
+ this.places,
1696
+ boundTransitions,
1697
+ this.subnetMembership
1698
+ );
1680
1699
  }
1681
1700
  static builder(name) {
1682
1701
  return new PetriNetBuilder(name);
1683
1702
  }
1684
1703
  };
1685
- var PetriNetBuilder = class {
1704
+ var PetriNetBuilder = class _PetriNetBuilder {
1686
1705
  _name;
1687
1706
  _places = /* @__PURE__ */ new Set();
1688
1707
  _transitions = /* @__PURE__ */ new Set();
1689
1708
  _fusionSets = [];
1709
+ // MOD-026: node name -> set of subnet names that contributed it via
1710
+ // compose(SubnetDef), in first-contribution order. Resolved to single-owner
1711
+ // membership at build(). V8 Map/Set iterate in insertion order, preserving
1712
+ // compose order for cross-language byte-parity.
1713
+ _subnetContributions = /* @__PURE__ */ new Map();
1690
1714
  constructor(name) {
1691
1715
  this._name = name;
1692
1716
  }
@@ -1795,17 +1819,29 @@ var PetriNetBuilder = class {
1795
1819
  }
1796
1820
  const hostByName = /* @__PURE__ */ new Map();
1797
1821
  for (const p of this._places) hostByName.set(p.name, p);
1822
+ const subnetName = def.name.replace(/\//g, "_");
1798
1823
  const mergeMap = /* @__PURE__ */ new Map();
1799
1824
  for (const p of body.places) {
1800
1825
  const host = hostByName.get(p.name);
1801
1826
  this.place(host ?? p);
1802
1827
  if (host !== void 0) mergeMap.set(p.name, host);
1828
+ this.recordContribution(p.name, subnetName);
1803
1829
  }
1804
1830
  for (const t of body.transitions) {
1805
1831
  this.transition(substitutePlaces(t, mergeMap));
1832
+ this.recordContribution(t.name, subnetName);
1806
1833
  }
1807
1834
  return this;
1808
1835
  }
1836
+ /** @internal MOD-026: records a node as contributed by the named subnet. */
1837
+ recordContribution(nodeName, subnetName) {
1838
+ let owners = this._subnetContributions.get(nodeName);
1839
+ if (owners === void 0) {
1840
+ owners = /* @__PURE__ */ new Set();
1841
+ this._subnetContributions.set(nodeName, owners);
1842
+ }
1843
+ owners.add(subnetName);
1844
+ }
1809
1845
  /**
1810
1846
  * Identity-default auto-compose per **MOD-024**.
1811
1847
  *
@@ -1990,16 +2026,58 @@ var PetriNetBuilder = class {
1990
2026
  * @throws when two fusion sets share a place
1991
2027
  */
1992
2028
  build() {
2029
+ const membership = this.resolveSubnetMembership();
1993
2030
  if (this._fusionSets.length === 0) {
1994
- return new PetriNet(PETRI_NET_KEY, this._name, this._places, this._transitions);
2031
+ return new PetriNet(
2032
+ PETRI_NET_KEY,
2033
+ this._name,
2034
+ this._places,
2035
+ this._transitions,
2036
+ membership
2037
+ );
1995
2038
  }
1996
- return this.buildWithFusion();
2039
+ return this.buildWithFusion(membership);
2040
+ }
2041
+ /**
2042
+ * @internal Resolves the per-compose contributions recorded by
2043
+ * `composeDirect` into the final node-name → subnet-name membership map per
2044
+ * **MOD-026**. A node contributed by exactly one subnet maps to that subnet;
2045
+ * a place contributed by two or more subnets is a shared rendezvous place
2046
+ * and is omitted (it renders top-level, outside any cluster). Returns an
2047
+ * empty map — the common case — when no subnet was composed directly.
2048
+ */
2049
+ resolveSubnetMembership() {
2050
+ const resolved = /* @__PURE__ */ new Map();
2051
+ for (const [nodeName, owners] of this._subnetContributions) {
2052
+ if (owners.size === 1) {
2053
+ resolved.set(nodeName, owners.values().next().value);
2054
+ }
2055
+ }
2056
+ return resolved;
2057
+ }
2058
+ /**
2059
+ * @internal Drops membership entries for places removed by fusion: a
2060
+ * non-canonical fused member no longer exists in the net, so its
2061
+ * `node-name → subnet` entry would dangle. The surviving canonical place
2062
+ * keeps its own entry. Per **MOD-026**.
2063
+ */
2064
+ static filterFusedMembership(membership, nonCanonicalNames) {
2065
+ if (membership.size === 0 || nonCanonicalNames.size === 0) {
2066
+ return membership;
2067
+ }
2068
+ const filtered = /* @__PURE__ */ new Map();
2069
+ for (const [key, value] of membership) {
2070
+ if (!nonCanonicalNames.has(key)) {
2071
+ filtered.set(key, value);
2072
+ }
2073
+ }
2074
+ return filtered;
1997
2075
  }
1998
2076
  /**
1999
2077
  * @internal Fusion-resolution pass per **MOD-061**. Split out from
2000
2078
  * {@link build} so the no-fusion fast path stays trivial.
2001
2079
  */
2002
- buildWithFusion() {
2080
+ buildWithFusion(membership) {
2003
2081
  const ownership = /* @__PURE__ */ new Map();
2004
2082
  for (const set of this._fusionSets) {
2005
2083
  for (const member of set.members) {
@@ -2035,7 +2113,13 @@ var PetriNetBuilder = class {
2035
2113
  for (const r of t.reads) rebuiltPlaces.add(r.place);
2036
2114
  for (const r of t.resets) rebuiltPlaces.add(r.place);
2037
2115
  }
2038
- return new PetriNet(PETRI_NET_KEY, this._name, rebuiltPlaces, rewrittenTransitions);
2116
+ return new PetriNet(
2117
+ PETRI_NET_KEY,
2118
+ this._name,
2119
+ rebuiltPlaces,
2120
+ rewrittenTransitions,
2121
+ _PetriNetBuilder.filterFusedMembership(membership, nonCanonicalNames)
2122
+ );
2039
2123
  }
2040
2124
  };
2041
2125
  function rebuildWithAction(t, action) {
@@ -2445,6 +2529,7 @@ var OutViolationError = class extends Error {
2445
2529
  };
2446
2530
 
2447
2531
  // src/runtime/executor-support.ts
2532
+ var DEADLINE_TOLERANCE_MS = 5;
2448
2533
  function validateOutSpec(tName, spec, producedPlaceNames) {
2449
2534
  switch (spec.type) {
2450
2535
  case "place":
@@ -2505,7 +2590,6 @@ function produceTimeoutOutput(context, timeoutChild) {
2505
2590
  }
2506
2591
 
2507
2592
  // src/runtime/bitmap-net-executor.ts
2508
- var DEADLINE_TOLERANCE_MS = 5;
2509
2593
  var BitmapNetExecutor = class {
2510
2594
  compiled;
2511
2595
  marking;
@@ -2530,6 +2614,10 @@ var BitmapNetExecutor = class {
2530
2614
  enabledFlags;
2531
2615
  /** Precomputed: 1 if transition has a finite deadline, 0 otherwise. */
2532
2616
  hasDeadlineFlags;
2617
+ /** Precomputed: 1 for exact() transitions — enforced softly, never force-disabled (TIME-006). */
2618
+ isExactFlags;
2619
+ /** Grace band (ms) before a hard deadline force-disables (TIME-013). */
2620
+ deadlineToleranceMs;
2533
2621
  enabledTransitionCount = 0;
2534
2622
  // In-flight tracking
2535
2623
  inFlight = /* @__PURE__ */ new Map();
@@ -2557,6 +2645,10 @@ var BitmapNetExecutor = class {
2557
2645
  );
2558
2646
  this.hasEnvironmentPlaces = this.environmentPlaces.size > 0;
2559
2647
  this.executionContextProvider = options.executionContextProvider;
2648
+ this.deadlineToleranceMs = options.deadlineToleranceMs ?? DEADLINE_TOLERANCE_MS;
2649
+ if (this.deadlineToleranceMs < 0) {
2650
+ throw new Error(`Deadline tolerance must be non-negative: ${this.deadlineToleranceMs}`);
2651
+ }
2560
2652
  this.startMs = performance.now();
2561
2653
  const wordCount = this.compiled.wordCount;
2562
2654
  this.markedPlaces = new Uint32Array(wordCount);
@@ -2570,6 +2662,7 @@ var BitmapNetExecutor = class {
2570
2662
  this.inFlightFlags = new Uint8Array(this.compiled.transitionCount);
2571
2663
  this.enabledFlags = new Uint8Array(this.compiled.transitionCount);
2572
2664
  this.hasDeadlineFlags = new Uint8Array(this.compiled.transitionCount);
2665
+ this.isExactFlags = new Uint8Array(this.compiled.transitionCount);
2573
2666
  let anyDeadlines = false;
2574
2667
  let allImm = true;
2575
2668
  let samePrio = true;
@@ -2580,6 +2673,7 @@ var BitmapNetExecutor = class {
2580
2673
  this.hasDeadlineFlags[tid] = 1;
2581
2674
  anyDeadlines = true;
2582
2675
  }
2676
+ if (t.timing.type === "exact") this.isExactFlags[tid] = 1;
2583
2677
  if (t.timing.type !== "immediate") allImm = false;
2584
2678
  if (t.priority !== firstPriority) samePrio = false;
2585
2679
  }
@@ -2759,11 +2853,12 @@ var BitmapNetExecutor = class {
2759
2853
  enforceDeadlines(nowMs) {
2760
2854
  for (let tid = 0; tid < this.compiled.transitionCount; tid++) {
2761
2855
  if (!this.hasDeadlineFlags[tid]) continue;
2856
+ if (this.isExactFlags[tid]) continue;
2762
2857
  if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
2763
2858
  const t = this.compiled.transition(tid);
2764
2859
  const elapsed = nowMs - this.enabledAtMs[tid];
2765
2860
  const latestMs = latest(t.timing);
2766
- if (elapsed > latestMs + DEADLINE_TOLERANCE_MS) {
2861
+ if (elapsed > latestMs + this.deadlineToleranceMs) {
2767
2862
  this.enabledFlags[tid] = 0;
2768
2863
  this.enabledTransitionCount--;
2769
2864
  this.emitEvent({
@@ -3280,6 +3375,12 @@ var PrecompiledNet = class _PrecompiledNet {
3280
3375
  earliestMs;
3281
3376
  latestMs;
3282
3377
  hasDeadline;
3378
+ /**
3379
+ * 1 for `exact()` transitions. Exact transitions fire at the first opportunity at/after their
3380
+ * target time (delayed-style liveness) and are never force-disabled by deadline enforcement —
3381
+ * their upper bound is observed, not enforced. See TIME-006.
3382
+ */
3383
+ isExact;
3283
3384
  // ==================== Priority (CONC-023) ====================
3284
3385
  priorities;
3285
3386
  transitionToPriorityIndex;
@@ -3400,6 +3501,7 @@ var PrecompiledNet = class _PrecompiledNet {
3400
3501
  this.earliestMs = new Float64Array(tc);
3401
3502
  this.latestMs = new Float64Array(tc);
3402
3503
  this.hasDeadline = new Uint8Array(tc);
3504
+ this.isExact = new Uint8Array(tc);
3403
3505
  let anyDeadlines = false;
3404
3506
  let allImm = true;
3405
3507
  for (let tid = 0; tid < tc; tid++) {
@@ -3410,6 +3512,7 @@ var PrecompiledNet = class _PrecompiledNet {
3410
3512
  this.hasDeadline[tid] = 1;
3411
3513
  anyDeadlines = true;
3412
3514
  }
3515
+ if (t.timing.type === "exact") this.isExact[tid] = 1;
3413
3516
  if (t.timing.type !== "immediate") allImm = false;
3414
3517
  }
3415
3518
  this.anyDeadlines = anyDeadlines;
@@ -3577,7 +3680,6 @@ function simpleOutputPlace(spec, compiled) {
3577
3680
  }
3578
3681
 
3579
3682
  // src/runtime/precompiled-net-executor.ts
3580
- var DEADLINE_TOLERANCE_MS2 = 5;
3581
3683
  var PrecompiledNetExecutor = class {
3582
3684
  program;
3583
3685
  eventStore;
@@ -3585,6 +3687,7 @@ var PrecompiledNetExecutor = class {
3585
3687
  hasEnvironmentPlaces;
3586
3688
  executionContextProvider;
3587
3689
  skipOutputValidation;
3690
+ deadlineToleranceMs;
3588
3691
  startMs;
3589
3692
  eventStoreEnabled;
3590
3693
  // ==================== Token Storage ====================
@@ -3638,6 +3741,10 @@ var PrecompiledNetExecutor = class {
3638
3741
  this.hasEnvironmentPlaces = this.environmentPlaces.size > 0;
3639
3742
  this.executionContextProvider = options.executionContextProvider;
3640
3743
  this.skipOutputValidation = options.skipOutputValidation ?? false;
3744
+ this.deadlineToleranceMs = options.deadlineToleranceMs ?? DEADLINE_TOLERANCE_MS;
3745
+ if (this.deadlineToleranceMs < 0) {
3746
+ throw new Error(`Deadline tolerance must be non-negative: ${this.deadlineToleranceMs}`);
3747
+ }
3641
3748
  this.startMs = performance.now();
3642
3749
  this.eventStoreEnabled = this.eventStore.isEnabled();
3643
3750
  const prog = this.program;
@@ -3852,10 +3959,11 @@ var PrecompiledNetExecutor = class {
3852
3959
  const tc = prog.transitionCount;
3853
3960
  for (let tid = 0; tid < tc; tid++) {
3854
3961
  if (!prog.hasDeadline[tid]) continue;
3962
+ if (prog.isExact[tid]) continue;
3855
3963
  if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
3856
3964
  const elapsed = nowMs - this.enabledAtMs[tid];
3857
3965
  const latestMs = prog.latestMs[tid];
3858
- if (elapsed > latestMs + DEADLINE_TOLERANCE_MS2) {
3966
+ if (elapsed > latestMs + this.deadlineToleranceMs) {
3859
3967
  this.enabledFlags[tid] = 0;
3860
3968
  this.enabledTransitionCount--;
3861
3969
  this.enabledAtMs[tid] = -Infinity;