libpetri 2.13.0 → 3.0.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.
package/dist/index.js CHANGED
@@ -1,18 +1,24 @@
1
1
  import {
2
2
  SmtVerifier,
3
+ all,
3
4
  allPlaces,
4
5
  and,
5
6
  andPlaces,
7
+ atLeast,
8
+ consumptionCount,
6
9
  enumerateBranches,
10
+ exactly,
7
11
  fork,
8
12
  forwardInput,
9
13
  isPassthrough,
10
14
  isProven,
11
15
  isViolated,
16
+ one,
12
17
  outPlace,
13
18
  passthrough,
14
19
  produce,
15
20
  requireOutputProducingActions,
21
+ requiredCount,
16
22
  timeout,
17
23
  timeoutPlace,
18
24
  transform,
@@ -21,7 +27,7 @@ import {
21
27
  withTimeout,
22
28
  xor,
23
29
  xorPlaces
24
- } from "./chunk-7VJ5CYUU.js";
30
+ } from "./chunk-5W6SVYPD.js";
25
31
  import {
26
32
  eventTransitionName,
27
33
  isFailureEvent
@@ -31,7 +37,7 @@ import {
31
37
  matchCorrelates,
32
38
  matchKey,
33
39
  matchSpec
34
- } from "./chunk-E3ZWB645.js";
40
+ } from "./chunk-YVIPJ6KM.js";
35
41
  import {
36
42
  MAX_DURATION_MS,
37
43
  deadline,
@@ -71,8 +77,8 @@ function environmentPlace(name) {
71
77
  }
72
78
 
73
79
  // src/core/arc.ts
74
- function inputArc(place2, guard) {
75
- return guard !== void 0 ? { type: "input", place: place2, guard } : { type: "input", place: place2 };
80
+ function inputArc(place2) {
81
+ return { type: "input", place: place2 };
76
82
  }
77
83
  function outputArc(place2) {
78
84
  return { type: "output", place: place2 };
@@ -89,62 +95,6 @@ function resetArc(place2) {
89
95
  function arcPlace(arc) {
90
96
  return arc.place;
91
97
  }
92
- function hasGuard(arc) {
93
- return arc.guard !== void 0;
94
- }
95
- function matchesGuard(arc, value) {
96
- if (arc.guard === void 0) return true;
97
- return arc.guard(value);
98
- }
99
-
100
- // src/core/in.ts
101
- function one(place2, guard) {
102
- return guard !== void 0 ? { type: "one", place: place2, guard } : { type: "one", place: place2 };
103
- }
104
- function exactly(count, place2, guard) {
105
- if (count < 1) {
106
- throw new Error(`count must be >= 1, got: ${count}`);
107
- }
108
- return guard !== void 0 ? { type: "exactly", place: place2, count, guard } : { type: "exactly", place: place2, count };
109
- }
110
- function all(place2, guard) {
111
- return guard !== void 0 ? { type: "all", place: place2, guard } : { type: "all", place: place2 };
112
- }
113
- function atLeast(minimum, place2, guard) {
114
- if (minimum < 1) {
115
- throw new Error(`minimum must be >= 1, got: ${minimum}`);
116
- }
117
- return guard !== void 0 ? { type: "at-least", place: place2, minimum, guard } : { type: "at-least", place: place2, minimum };
118
- }
119
- function requiredCount(spec) {
120
- switch (spec.type) {
121
- case "one":
122
- return 1;
123
- case "exactly":
124
- return spec.count;
125
- case "all":
126
- return 1;
127
- case "at-least":
128
- return spec.minimum;
129
- }
130
- }
131
- function consumptionCount(spec, available) {
132
- if (available < requiredCount(spec)) {
133
- throw new Error(
134
- `Cannot consume from '${spec.place.name}': available=${available}, required=${requiredCount(spec)}`
135
- );
136
- }
137
- switch (spec.type) {
138
- case "one":
139
- return 1;
140
- case "exactly":
141
- return spec.count;
142
- case "all":
143
- return available;
144
- case "at-least":
145
- return available;
146
- }
147
- }
148
98
 
149
99
  // src/core/name.ts
150
100
  function nameId(value) {
@@ -1001,7 +951,7 @@ function rewriteTransition(t, prefix, placeRemap) {
1001
951
  function substitutePlaces(t, remap) {
1002
952
  return rebuildWithName(t, t.name, remap);
1003
953
  }
1004
- function rebuildWithName(t, name, remap) {
954
+ function rebuildWithName(t, name, remap, normalizeInputs) {
1005
955
  const builder = Transition.builder(name).timing(t.timing).priority(t.priority).action(t.action);
1006
956
  const alias = buildPlaceAlias(t, remap);
1007
957
  if (alias.size > 0) {
@@ -1012,7 +962,7 @@ function rebuildWithName(t, name, remap) {
1012
962
  for (let i = 0; i < t.inputSpecs.length; i++) {
1013
963
  rewrittenInputs[i] = rewriteIn(t.inputSpecs[i], remap);
1014
964
  }
1015
- builder.inputs(...rewrittenInputs);
965
+ builder.inputs(...normalizeInputs !== void 0 ? normalizeInputs(rewrittenInputs) : rewrittenInputs);
1016
966
  }
1017
967
  if (t.outputSpec !== null) {
1018
968
  builder.outputs(rewriteOut(t.outputSpec, remap));
@@ -1072,13 +1022,13 @@ var EMPTY_ALIAS2 = /* @__PURE__ */ new Map();
1072
1022
  function rewriteIn(spec, remap) {
1073
1023
  switch (spec.type) {
1074
1024
  case "one":
1075
- return one(resolve(spec.place, remap), spec.guard);
1025
+ return one(resolve(spec.place, remap));
1076
1026
  case "exactly":
1077
- return exactly(spec.count, resolve(spec.place, remap), spec.guard);
1027
+ return exactly(spec.count, resolve(spec.place, remap));
1078
1028
  case "all":
1079
- return all(resolve(spec.place, remap), spec.guard);
1029
+ return all(resolve(spec.place, remap));
1080
1030
  case "at-least":
1081
- return atLeast(spec.minimum, resolve(spec.place, remap), spec.guard);
1031
+ return atLeast(spec.minimum, resolve(spec.place, remap));
1082
1032
  }
1083
1033
  }
1084
1034
  function rewriteOut(out, remap) {
@@ -1133,7 +1083,11 @@ function mergeTransitions(caller, instance, mergedName) {
1133
1083
  if (mergedAction !== void 0) {
1134
1084
  builder.action(mergedAction);
1135
1085
  }
1136
- const unionedInputs = unionArcs(caller.inputSpecs, instance.inputSpecs, keyOfIn);
1086
+ rejectCrossSideKindConflicts(caller, instance, mergedName);
1087
+ const unionedInputs = normalizeInputArcs(
1088
+ [...caller.inputSpecs, ...instance.inputSpecs],
1089
+ () => `Channel composition '${mergedName}'`
1090
+ );
1137
1091
  if (unionedInputs.length > 0) {
1138
1092
  builder.inputs(...unionedInputs);
1139
1093
  }
@@ -1264,44 +1218,128 @@ function describeTiming(t) {
1264
1218
  return `Exact(atMs=${t.atMs})`;
1265
1219
  }
1266
1220
  }
1267
- function keyOfIn(arc) {
1221
+ function keyOfInhibitor(arc) {
1222
+ return `inh|${arc.place.name}`;
1223
+ }
1224
+ function keyOfRead(arc) {
1225
+ return `read|${arc.place.name}`;
1226
+ }
1227
+ function keyOfReset(arc) {
1228
+ return `reset|${arc.place.name}`;
1229
+ }
1230
+ function normalizeInputArcs(arcs, seamOf) {
1231
+ if (arcs.length < 2) return arcs;
1232
+ const byPlace = /* @__PURE__ */ new Map();
1233
+ let collided = false;
1234
+ for (let i2 = 0; i2 < arcs.length; i2++) {
1235
+ const arc = arcs[i2];
1236
+ const name = arc.place.name;
1237
+ const prior = byPlace.get(name);
1238
+ if (prior === void 0) {
1239
+ byPlace.set(name, arc);
1240
+ } else {
1241
+ collided = true;
1242
+ byPlace.set(name, mergeInPair(prior, arc, seamOf(name)));
1243
+ }
1244
+ }
1245
+ if (!collided) return arcs;
1246
+ const result = new Array(byPlace.size);
1247
+ let i = 0;
1248
+ for (const arc of byPlace.values()) result[i++] = arc;
1249
+ return result;
1250
+ }
1251
+ function mergeInPair(a, b, seam) {
1252
+ if (a.type === "all" && b.type === "all") return a;
1253
+ if (a.type === "at-least" && b.type === "at-least") {
1254
+ return a.minimum >= b.minimum ? a : b;
1255
+ }
1256
+ const countA = summableCount(a);
1257
+ const countB = summableCount(b);
1258
+ if (countA !== -1 && countB !== -1) {
1259
+ return exactly(countA + countB, a.place);
1260
+ }
1261
+ throw new Error(
1262
+ `${seam}: input arcs ${describeIn(a)} and ${describeIn(b)} collide on place '${a.place.name}' and have no additive merge (MOD-021 rule (c)). Use a single arc with exactly(n) / atLeast(n).`
1263
+ );
1264
+ }
1265
+ function summableCount(arc) {
1266
+ switch (arc.type) {
1267
+ case "one":
1268
+ return 1;
1269
+ case "exactly":
1270
+ return arc.count;
1271
+ default:
1272
+ return -1;
1273
+ }
1274
+ }
1275
+ function describeIn(arc) {
1268
1276
  switch (arc.type) {
1269
1277
  case "one":
1270
- return arc.guard === void 0 ? `one|${arc.place.name}` : `one|${arc.place.name}|g:${guardKey(arc.guard)}`;
1278
+ return "one()";
1271
1279
  case "exactly":
1272
- return arc.guard === void 0 ? `exactly|${arc.place.name}|${arc.count}` : `exactly|${arc.place.name}|${arc.count}|g:${guardKey(arc.guard)}`;
1280
+ return `exactly(${arc.count})`;
1273
1281
  case "all":
1274
- return arc.guard === void 0 ? `all|${arc.place.name}` : `all|${arc.place.name}|g:${guardKey(arc.guard)}`;
1282
+ return "all()";
1275
1283
  case "at-least":
1276
- return arc.guard === void 0 ? `atLeast|${arc.place.name}|${arc.minimum}` : `atLeast|${arc.place.name}|${arc.minimum}|g:${guardKey(arc.guard)}`;
1284
+ return `atLeast(${arc.minimum})`;
1277
1285
  }
1278
1286
  }
1279
- function keyOfInhibitor(arc) {
1280
- return `inh|${arc.place.name}`;
1287
+ function rejectCrossSideKindConflicts(caller, instance, mergedName) {
1288
+ const callerKinds = arcKindsByPlace(caller);
1289
+ if (callerKinds.size === 0) return;
1290
+ for (const [place2, instanceSet] of arcKindsByPlace(instance)) {
1291
+ const callerSet = callerKinds.get(place2);
1292
+ if (callerSet !== void 0 && !sameKindSet(callerSet, instanceSet)) {
1293
+ throw new Error(
1294
+ `Channel composition '${mergedName}': conflicting arc kinds on place '${place2}' \u2014 caller-side ${describeKinds(callerSet)} vs instance-side ${describeKinds(instanceSet)}. Different arc types on one place cannot be merged (MOD-021 rule (d)). Resolve explicitly.`
1295
+ );
1296
+ }
1297
+ }
1281
1298
  }
1282
- function keyOfRead(arc) {
1283
- return `read|${arc.place.name}`;
1299
+ function arcKindsByPlace(t) {
1300
+ const kinds = /* @__PURE__ */ new Map();
1301
+ const add = (name, kind) => {
1302
+ let set = kinds.get(name);
1303
+ if (set === void 0) {
1304
+ set = /* @__PURE__ */ new Set();
1305
+ kinds.set(name, set);
1306
+ }
1307
+ set.add(kind);
1308
+ };
1309
+ for (const s of t.inputSpecs) add(s.place.name, "input");
1310
+ for (const a of t.inhibitors) add(a.place.name, "inhibitor");
1311
+ for (const a of t.reads) add(a.place.name, "read");
1312
+ for (const a of t.resets) add(a.place.name, "reset");
1313
+ return kinds;
1284
1314
  }
1285
- function keyOfReset(arc) {
1286
- return `reset|${arc.place.name}`;
1315
+ function describeKinds(kinds) {
1316
+ return `[${[...kinds].sort().join(", ")}]`;
1287
1317
  }
1288
- var GUARD_KEYS = /* @__PURE__ */ new WeakMap();
1289
- var guardKeyCounter = 0;
1290
- function guardKey(g) {
1291
- let k = GUARD_KEYS.get(g);
1292
- if (k === void 0) {
1293
- k = String(++guardKeyCounter);
1294
- GUARD_KEYS.set(g, k);
1295
- }
1296
- return k;
1318
+ function sameKindSet(a, b) {
1319
+ if (a.size !== b.size) return false;
1320
+ for (const k of a) if (!b.has(k)) return false;
1321
+ return true;
1297
1322
  }
1298
- function applyFusion(transitions, fusionMap) {
1323
+ function applyFusion(transitions, fusionMap, seamOf) {
1324
+ const normalizeInputs = (inputs) => normalizeInputArcs(inputs, seamOf);
1299
1325
  const rewritten = /* @__PURE__ */ new Set();
1300
1326
  for (const t of transitions) {
1301
- rewritten.add(substitutePlaces(t, fusionMap));
1327
+ rewritten.add(rebuildWithName(
1328
+ t,
1329
+ t.name,
1330
+ fusionMap,
1331
+ fusionTouchesInputs(t, fusionMap) ? normalizeInputs : void 0
1332
+ ));
1302
1333
  }
1303
1334
  return rewritten;
1304
1335
  }
1336
+ function fusionTouchesInputs(t, fusionMap) {
1337
+ if (fusionMap.size === 0) return false;
1338
+ for (let i = 0; i < t.inputSpecs.length; i++) {
1339
+ if (fusionMap.has(t.inputSpecs[i].place.name)) return true;
1340
+ }
1341
+ return false;
1342
+ }
1305
1343
 
1306
1344
  // src/verification/verification-harness.ts
1307
1345
  function buildVerificationResult(syntheticNet, perProperty) {
@@ -2197,7 +2235,8 @@ var PetriNetBuilder = class _PetriNetBuilder {
2197
2235
  * Map<string, Place<unknown>> convention — TypeScript Place identity is
2198
2236
  * name-based per `runtime/compiled-net.ts`).
2199
2237
  * 3. Walk every transition through {@link applyFusion} to rewrite arc place
2200
- * references.
2238
+ * references; input arcs colliding on a canonical place merge per
2239
+ * [MOD-021] (additive where summable, rejected otherwise).
2201
2240
  * 4. Re-derive the place set from the rewritten transitions plus any
2202
2241
  * caller-declared standalone places, dropping non-canonical members.
2203
2242
  * Caller-declared standalone places that happen to be non-canonical
@@ -2283,7 +2322,10 @@ var PetriNetBuilder = class _PetriNetBuilder {
2283
2322
  nonCanonicalNames.add(nc.name);
2284
2323
  }
2285
2324
  }
2286
- const rewrittenTransitions = applyFusion(this._transitions, fusionMap);
2325
+ const rewrittenTransitions = applyFusion(this._transitions, fusionMap, (canonicalName) => {
2326
+ const owner = ownership.get(canonicalName);
2327
+ return `Fusion set '${owner !== void 0 ? owner.name : canonicalName}'`;
2328
+ });
2287
2329
  const rebuiltPlaces = /* @__PURE__ */ new Set();
2288
2330
  for (const p of this._places) {
2289
2331
  if (!nonCanonicalNames.has(p.name)) {
@@ -2384,21 +2426,21 @@ var Marking = class _Marking {
2384
2426
  return result;
2385
2427
  }
2386
2428
  /**
2387
- * Removes and returns the first token whose value satisfies the guard predicate.
2429
+ * Removes and returns the first token whose value satisfies the predicate.
2388
2430
  *
2389
- * Performs a linear scan of the place's FIFO queue. If no guard is provided,
2390
- * behaves like `removeFirst()`. If a guard is provided, skips non-matching
2431
+ * Performs a linear scan of the place's FIFO queue. If no predicate is
2432
+ * provided, behaves like `removeFirst()`. Otherwise skips non-matching
2391
2433
  * tokens and splices the first match out of the queue (O(n) worst case).
2392
2434
  */
2393
2435
  removeFirstMatching(spec) {
2394
2436
  const queue = this.tokens.get(spec.place.name);
2395
2437
  if (!queue || queue.length === 0) return null;
2396
- if (!spec.guard) {
2438
+ if (!spec.predicate) {
2397
2439
  return queue.shift();
2398
2440
  }
2399
2441
  for (let i = 0; i < queue.length; i++) {
2400
2442
  const token = queue[i];
2401
- if (spec.guard(token.value)) {
2443
+ if (spec.predicate(token.value)) {
2402
2444
  if (i === 0) queue.shift();
2403
2445
  else queue.splice(i, 1);
2404
2446
  return token;
@@ -2407,27 +2449,27 @@ var Marking = class _Marking {
2407
2449
  return null;
2408
2450
  }
2409
2451
  // ======================== Token Inspection ========================
2410
- /** Check if any token matches a guard predicate. */
2452
+ /** Check if any token satisfies the predicate. */
2411
2453
  hasMatchingToken(spec) {
2412
2454
  const queue = this.tokens.get(spec.place.name);
2413
2455
  if (!queue || queue.length === 0) return false;
2414
- if (!spec.guard) return true;
2415
- return queue.some((t) => spec.guard(t.value));
2456
+ if (!spec.predicate) return true;
2457
+ return queue.some((t) => spec.predicate(t.value));
2416
2458
  }
2417
2459
  /**
2418
- * Counts tokens in a place whose values satisfy the guard predicate.
2460
+ * Counts tokens in a place whose values satisfy the predicate.
2419
2461
  *
2420
- * If no guard is provided, returns the total token count (O(1)).
2421
- * With a guard, performs a linear scan over all tokens (O(n)).
2422
- * Used by the executor for enablement checks on guarded `all`/`at-least` inputs.
2462
+ * If no predicate is provided, returns the total token count (O(1)).
2463
+ * With a predicate, performs a linear scan over all tokens (O(n)).
2464
+ * Used by the executor to size a ν-net correlated `all`/`at-least` consume.
2423
2465
  */
2424
2466
  countMatching(spec) {
2425
2467
  const queue = this.tokens.get(spec.place.name);
2426
2468
  if (!queue || queue.length === 0) return 0;
2427
- if (!spec.guard) return queue.length;
2469
+ if (!spec.predicate) return queue.length;
2428
2470
  let count = 0;
2429
2471
  for (const t of queue) {
2430
- if (spec.guard(t.value)) count++;
2472
+ if (spec.predicate(t.value)) count++;
2431
2473
  }
2432
2474
  return count;
2433
2475
  }
@@ -2486,12 +2528,11 @@ var CompiledNet = class _CompiledNet {
2486
2528
  _placeToTransitions;
2487
2529
  // Consumption place IDs per transition (input + reset places)
2488
2530
  _consumptionPlaceIds;
2489
- // Cardinality and guard flags
2531
+ // Cardinality flags
2490
2532
  _cardinalityChecks;
2491
- _hasGuards;
2492
2533
  // ν-net join flag: the transition carries a MatchSpec (NU-020). Precomputed
2493
2534
  // so the hot enablement loop can skip the match check on non-ν transitions
2494
- // (zero-cost gating), mirroring `_hasGuards`.
2535
+ // (zero-cost gating).
2495
2536
  _hasMatch;
2496
2537
  constructor(net) {
2497
2538
  this.net = net;
@@ -2524,7 +2565,6 @@ var CompiledNet = class _CompiledNet {
2524
2565
  this._inhibitorMask = new Array(this.transitionCount);
2525
2566
  this._consumptionPlaceIds = new Array(this.transitionCount);
2526
2567
  this._cardinalityChecks = new Array(this.transitionCount).fill(null);
2527
- this._hasGuards = new Array(this.transitionCount).fill(false);
2528
2568
  this._hasMatch = new Array(this.transitionCount).fill(false);
2529
2569
  const placeToTransitionsList = new Array(this.placeCount);
2530
2570
  for (let i = 0; i < this.placeCount; i++) {
@@ -2536,16 +2576,20 @@ var CompiledNet = class _CompiledNet {
2536
2576
  const needs = new Uint32Array(this.wordCount);
2537
2577
  const inhibitors = new Uint32Array(this.wordCount);
2538
2578
  let needsCardinality = false;
2579
+ const seenInputPlaces = /* @__PURE__ */ new Set();
2539
2580
  for (const inSpec of t.inputSpecs) {
2581
+ if (seenInputPlaces.has(inSpec.place.name)) {
2582
+ throw new Error(
2583
+ `Transition '${t.name}' declares two input arcs on place '${inSpec.place.name}'. Duplicate input places have no coherent consumption semantics and are rejected at compile time (CORE-030). Use a single arc with exactly(n) / atLeast(n) instead.`
2584
+ );
2585
+ }
2586
+ seenInputPlaces.add(inSpec.place.name);
2540
2587
  const pid = this._placeIndex.get(inSpec.place.name);
2541
2588
  setBit(needs, pid);
2542
2589
  placeToTransitionsList[pid].push(tid);
2543
2590
  if (inSpec.type !== "one") {
2544
2591
  needsCardinality = true;
2545
2592
  }
2546
- if (inSpec.guard) {
2547
- this._hasGuards[tid] = true;
2548
- }
2549
2593
  }
2550
2594
  if (needsCardinality) {
2551
2595
  const pids = [];
@@ -2597,6 +2641,14 @@ var CompiledNet = class _CompiledNet {
2597
2641
  if (id === void 0) throw new Error(`Unknown place: ${place2.name}`);
2598
2642
  return id;
2599
2643
  }
2644
+ /**
2645
+ * Non-throwing variant of {@link placeId}: `undefined` when the compiled net
2646
+ * does not know the place. Lets callers retain tokens on undeclared places
2647
+ * (CORE-072) instead of rejecting or dropping them.
2648
+ */
2649
+ tryPlaceId(place2) {
2650
+ return this._placeIndex.get(place2.name);
2651
+ }
2600
2652
  transitionId(t) {
2601
2653
  const id = this._transitionIndex.get(t);
2602
2654
  if (id === void 0) throw new Error(`Unknown transition: ${t.name}`);
@@ -2611,9 +2663,6 @@ var CompiledNet = class _CompiledNet {
2611
2663
  cardinalityCheck(tid) {
2612
2664
  return this._cardinalityChecks[tid];
2613
2665
  }
2614
- hasGuards(tid) {
2615
- return this._hasGuards[tid];
2616
- }
2617
2666
  hasMatch(tid) {
2618
2667
  return this._hasMatch[tid];
2619
2668
  }
@@ -2625,8 +2674,9 @@ var CompiledNet = class _CompiledNet {
2625
2674
  * 2. **Inhibitor check**: verifies no inhibitor places have tokens
2626
2675
  * via `!intersects(snapshot, inhibitorMask)`.
2627
2676
  *
2628
- * This is a necessary but not sufficient condition — cardinality and guard checks
2629
- * are performed separately by the executor for transitions that pass this fast path.
2677
+ * This is a necessary but not sufficient condition — cardinality and ν-net match
2678
+ * checks are performed separately by the executor for transitions that pass this
2679
+ * fast path.
2630
2680
  */
2631
2681
  canEnableBitmap(tid, markingSnapshot) {
2632
2682
  if (!containsAll(markingSnapshot, this._needsMask[tid])) return false;
@@ -2751,10 +2801,9 @@ function selectMatchName(perPlace, requireds) {
2751
2801
  }
2752
2802
  return bestName;
2753
2803
  }
2754
- function buildNameIndex(tokens, key, guard) {
2804
+ function buildNameIndex(tokens, key) {
2755
2805
  const index = /* @__PURE__ */ new Map();
2756
2806
  for (const token of tokens) {
2757
- if (guard && !guard(token.value)) continue;
2758
2807
  const name = key(token.value);
2759
2808
  if (name === void 0 || name === null) continue;
2760
2809
  const ts = token.createdAt;
@@ -2784,17 +2833,11 @@ function findBinding(t, getTokens) {
2784
2833
  const perPlace = [];
2785
2834
  const requireds = [];
2786
2835
  for (const k of ms.keys) {
2787
- perPlace.push(buildNameIndex(getTokens(k.place), k.key, guardFor(t, k.place.name)));
2836
+ perPlace.push(buildNameIndex(getTokens(k.place), k.key));
2788
2837
  requireds.push(requiredFor(t, k.place.name));
2789
2838
  }
2790
2839
  return selectMatchName(perPlace, requireds);
2791
2840
  }
2792
- function guardFor(t, placeName) {
2793
- for (const inSpec of t.inputSpecs) {
2794
- if (inSpec.place.name === placeName) return inSpec.guard;
2795
- }
2796
- return void 0;
2797
- }
2798
2841
  var MinQueue = class {
2799
2842
  inStack = [];
2800
2843
  inMin = [];
@@ -2890,7 +2933,7 @@ var IncrementalMatcher = class {
2890
2933
  heaped = false;
2891
2934
  heap = new ReadyHeap();
2892
2935
  ready = /* @__PURE__ */ new Map();
2893
- /** Add one (already guard-passing) token carrying `name` at `createdAt` to input `i`. */
2936
+ /** Add one token carrying `name` at `createdAt` to input `i`. */
2894
2937
  add(i, name, createdAt) {
2895
2938
  let q = this.ts[i].get(name);
2896
2939
  if (!q) {
@@ -3052,9 +3095,7 @@ function validateOutSpec(tName, spec, producedPlaceNames) {
3052
3095
  );
3053
3096
  }
3054
3097
  if (satisfied.length > 1) {
3055
- throw new OutViolationError(
3056
- `'${tName}': XOR violation - multiple branches produced`
3057
- );
3098
+ return resolveXorAmbiguity(tName, satisfied);
3058
3099
  }
3059
3100
  return satisfied[0];
3060
3101
  }
@@ -3062,14 +3103,26 @@ function validateOutSpec(tName, spec, producedPlaceNames) {
3062
3103
  return validateOutSpec(tName, spec.child, producedPlaceNames);
3063
3104
  }
3064
3105
  }
3106
+ function resolveXorAmbiguity(tName, satisfied) {
3107
+ const subsuming = satisfied.filter(
3108
+ (candidate) => satisfied.every(
3109
+ (other) => other === candidate || [...other].every((p) => candidate.has(p))
3110
+ )
3111
+ );
3112
+ if (subsuming.length === 1) return subsuming[0];
3113
+ throw new OutViolationError(
3114
+ `'${tName}': XOR violation - multiple branches produced`
3115
+ );
3116
+ }
3065
3117
  function produceTimeoutOutput(context, timeoutChild) {
3066
3118
  switch (timeoutChild.type) {
3067
3119
  case "place":
3068
3120
  context.outputToHarvest(timeoutChild.place, null);
3069
3121
  break;
3070
3122
  case "forward-input": {
3071
- const value = context.input(timeoutChild.from);
3072
- context.outputToHarvest(timeoutChild.to, value);
3123
+ for (const value of context.inputs(timeoutChild.from)) {
3124
+ context.outputToHarvest(timeoutChild.to, value);
3125
+ }
3073
3126
  break;
3074
3127
  }
3075
3128
  case "and":
@@ -3110,7 +3163,7 @@ var BitmapNetExecutor = class {
3110
3163
  allSamePriority;
3111
3164
  eventStoreEnabled;
3112
3165
  // Bitmaps (Uint32Array, direct writes)
3113
- markedPlaces;
3166
+ markingBitmap;
3114
3167
  dirtySet;
3115
3168
  markingSnapBuffer;
3116
3169
  dirtySnapBuffer;
@@ -3139,6 +3192,11 @@ var BitmapNetExecutor = class {
3139
3192
  readyBuffer = [];
3140
3193
  // Pending reset places for clock-restart detection
3141
3194
  pendingResetPlaces = /* @__PURE__ */ new Set();
3195
+ /**
3196
+ * Undeclared place names already reported (CORE-072 AC4). Keyed by name — TS
3197
+ * Place identity is name-based — so a hot loop warns once, not per token.
3198
+ */
3199
+ warnedUnknownPlaces = /* @__PURE__ */ new Set();
3142
3200
  transitionInputPlaceNames;
3143
3201
  running = false;
3144
3202
  draining = false;
@@ -3158,7 +3216,7 @@ var BitmapNetExecutor = class {
3158
3216
  }
3159
3217
  this.startMs = performance.now();
3160
3218
  const wordCount = this.compiled.wordCount;
3161
- this.markedPlaces = new Uint32Array(wordCount);
3219
+ this.markingBitmap = new Uint32Array(wordCount);
3162
3220
  this.markingSnapBuffer = new Uint32Array(wordCount);
3163
3221
  this.firingSnapBuffer = new Uint32Array(wordCount);
3164
3222
  const dirtyWords = this.compiled.transitionCount + BIT_MASK >>> WORD_SHIFT;
@@ -3194,6 +3252,11 @@ var BitmapNetExecutor = class {
3194
3252
  for (const spec of t.inputSpecs) names.add(spec.place.name);
3195
3253
  this.transitionInputPlaceNames.set(t, names);
3196
3254
  }
3255
+ for (const [place2, tokens] of initialTokens) {
3256
+ if (tokens.length > 0 && this.compiled.tryPlaceId(place2) === void 0) {
3257
+ this.warnUnknownPlace(place2, "");
3258
+ }
3259
+ }
3197
3260
  this.initMatchCaches();
3198
3261
  }
3199
3262
  /**
@@ -3252,9 +3315,7 @@ var BitmapNetExecutor = class {
3252
3315
  for (let keyIdx = 0; keyIdx < ms.keys.length; keyIdx++) {
3253
3316
  const mk = ms.keys[keyIdx];
3254
3317
  const pid = compiled.placeId(mk.place);
3255
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
3256
3318
  for (const token of this.marking.peekTokens(mk.place)) {
3257
- if (guard && !guard(token.value)) continue;
3258
3319
  const name = mk.key(token.value);
3259
3320
  if (name !== void 0 && name !== null) matcher.add(keyIdx, name, token.createdAt);
3260
3321
  }
@@ -3273,8 +3334,6 @@ var BitmapNetExecutor = class {
3273
3334
  if (cache == null) continue;
3274
3335
  const t = compiled.transition(tid);
3275
3336
  const mk = t.matchSpec.keys[keyIdx];
3276
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
3277
- if (guard && !guard(token.value)) continue;
3278
3337
  const name = mk.key(token.value);
3279
3338
  if (name !== void 0 && name !== null) cache.add(keyIdx, name, token.createdAt);
3280
3339
  }
@@ -3302,7 +3361,7 @@ var BitmapNetExecutor = class {
3302
3361
  netName: this.compiled.net.name,
3303
3362
  executionId: this.executionId()
3304
3363
  });
3305
- this.initializeMarkedBitmap();
3364
+ this.initializeMarkingBitmap();
3306
3365
  this.markAllDirty();
3307
3366
  this.emitEvent({
3308
3367
  type: "marking-snapshot",
@@ -3357,11 +3416,11 @@ var BitmapNetExecutor = class {
3357
3416
  return this.inject(envPlace, tokenOf(value));
3358
3417
  }
3359
3418
  // ======================== Initialize ========================
3360
- initializeMarkedBitmap() {
3419
+ initializeMarkingBitmap() {
3361
3420
  for (let pid = 0; pid < this.compiled.placeCount; pid++) {
3362
3421
  const place2 = this.compiled.place(pid);
3363
3422
  if (this.marking.hasTokens(place2)) {
3364
- setBit(this.markedPlaces, pid);
3423
+ setBit(this.markingBitmap, pid);
3365
3424
  }
3366
3425
  }
3367
3426
  }
@@ -3389,7 +3448,7 @@ var BitmapNetExecutor = class {
3389
3448
  updateDirtyTransitions() {
3390
3449
  const nowMs = performance.now();
3391
3450
  const markingSnap = this.markingSnapBuffer;
3392
- markingSnap.set(this.markedPlaces);
3451
+ markingSnap.set(this.markingBitmap);
3393
3452
  const dirtyWords = this.dirtySet.length;
3394
3453
  const dirtySnap = this.dirtySnapBuffer;
3395
3454
  for (let w = 0; w < dirtyWords; w++) {
@@ -3474,17 +3533,6 @@ var BitmapNetExecutor = class {
3474
3533
  if (this.marking.tokenCount(place2) < required) return false;
3475
3534
  }
3476
3535
  }
3477
- if (this.compiled.hasGuards(tid)) {
3478
- const t = this.compiled.transition(tid);
3479
- const cache = this.matchCaches[tid];
3480
- const ms = t.matchSpec;
3481
- for (const spec of t.inputSpecs) {
3482
- if (!spec.guard) continue;
3483
- if (cache != null && ms && keyForPlace(ms, spec.place.name) !== void 0) continue;
3484
- const requiredCount2 = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
3485
- if (this.marking.countMatching(spec) < requiredCount2) return false;
3486
- }
3487
- }
3488
3536
  if (this.compiled.hasMatch(tid)) {
3489
3537
  const cache = this.matchCaches[tid];
3490
3538
  const noBinding = cache != null ? cache.best() === null : findBinding(this.compiled.transition(tid), (p) => this.marking.peekTokens(p)) === null;
@@ -3515,7 +3563,7 @@ var BitmapNetExecutor = class {
3515
3563
  * Fast path for nets where all transitions are immediate and same priority.
3516
3564
  * Skips timing checks, sorting, and snapshot buffer — just scan and fire.
3517
3565
  *
3518
- * Uses live `markedPlaces` instead of a snapshot. Safe because
3566
+ * Uses live `markingBitmap` instead of a snapshot. Safe because
3519
3567
  * `updateBitmapAfterConsumption()` synchronously updates the bitmap before the next
3520
3568
  * iteration. For equal-priority immediate transitions, tid scan order satisfies
3521
3569
  * FIFO-by-enablement-time (all enabled in the same cycle).
@@ -3523,7 +3571,7 @@ var BitmapNetExecutor = class {
3523
3571
  fireReadyImmediate() {
3524
3572
  for (let tid = 0; tid < this.compiled.transitionCount; tid++) {
3525
3573
  if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
3526
- if (this.canEnable(tid, this.markedPlaces)) {
3574
+ if (this.canEnable(tid, this.markingBitmap)) {
3527
3575
  this.fireTransitionContained(tid);
3528
3576
  } else {
3529
3577
  this.enabledFlags[tid] = 0;
@@ -3552,12 +3600,12 @@ var BitmapNetExecutor = class {
3552
3600
  return a.enabledAtMs - b.enabledAtMs;
3553
3601
  });
3554
3602
  const freshSnap = this.firingSnapBuffer;
3555
- freshSnap.set(this.markedPlaces);
3603
+ freshSnap.set(this.markingBitmap);
3556
3604
  for (const entry of ready) {
3557
3605
  const { tid } = entry;
3558
3606
  if (this.enabledFlags[tid] && this.canEnable(tid, freshSnap)) {
3559
3607
  this.fireTransitionContained(tid);
3560
- freshSnap.set(this.markedPlaces);
3608
+ freshSnap.set(this.markingBitmap);
3561
3609
  } else {
3562
3610
  this.enabledFlags[tid] = 0;
3563
3611
  this.enabledTransitionCount--;
@@ -3573,7 +3621,7 @@ var BitmapNetExecutor = class {
3573
3621
  * EventStore.append on a token-removed or transition-started emit, or an error thrown while
3574
3622
  * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
3575
3623
  * The transition is instead failed and marked dirty for re-evaluation, the same treatment an
3576
- * asynchronously-reported failure gets. Enablement-phase throws (a guard or key function that
3624
+ * asynchronously-reported failure gets. Enablement-phase throws (a key function that
3577
3625
  * throws inside canEnable, before the firing) run outside this boundary and are not contained
3578
3626
  * here.
3579
3627
  *
@@ -3624,10 +3672,9 @@ var BitmapNetExecutor = class {
3624
3672
  const keyFn = ms ? keyForPlace(ms, inSpec.place.name) : void 0;
3625
3673
  let spec;
3626
3674
  if (keyFn && chosen !== null) {
3627
- const baseGuard = inSpec.guard;
3628
3675
  spec = {
3629
3676
  place: inSpec.place,
3630
- guard: (v) => (baseGuard ? baseGuard(v) : true) && keyFn(v) === chosen
3677
+ predicate: (v) => keyFn(v) === chosen
3631
3678
  };
3632
3679
  } else {
3633
3680
  spec = inSpec;
@@ -3642,11 +3689,11 @@ var BitmapNetExecutor = class {
3642
3689
  break;
3643
3690
  case "all":
3644
3691
  case "at-least":
3645
- toConsume = spec.guard ? this.marking.countMatching(spec) : this.marking.tokenCount(inSpec.place);
3692
+ toConsume = spec.predicate ? this.marking.countMatching(spec) : this.marking.tokenCount(inSpec.place);
3646
3693
  break;
3647
3694
  }
3648
3695
  for (let i = 0; i < toConsume; i++) {
3649
- const token = spec.guard ? this.marking.removeFirstMatching(spec) : this.marking.removeFirst(inSpec.place);
3696
+ const token = spec.predicate ? this.marking.removeFirstMatching(spec) : this.marking.removeFirst(inSpec.place);
3650
3697
  if (token === null) break;
3651
3698
  consumed.push(token);
3652
3699
  inputs.add(inSpec.place, token);
@@ -3770,7 +3817,7 @@ var BitmapNetExecutor = class {
3770
3817
  for (const pid of pids) {
3771
3818
  const place2 = this.compiled.place(pid);
3772
3819
  if (!this.marking.hasTokens(place2)) {
3773
- clearBit(this.markedPlaces, pid);
3820
+ clearBit(this.markingBitmap, pid);
3774
3821
  }
3775
3822
  this.markDirty(pid);
3776
3823
  }
@@ -3812,12 +3859,16 @@ var BitmapNetExecutor = class {
3812
3859
  }
3813
3860
  const produced = [];
3814
3861
  for (const entry of outputs.entries()) {
3815
- const pid = this.compiled.placeId(entry.place);
3816
- this.cacheAddToken(pid, entry.token);
3862
+ const pid = this.compiled.tryPlaceId(entry.place);
3817
3863
  this.marking.addToken(entry.place, entry.token);
3818
3864
  produced.push(entry.token);
3819
- setBit(this.markedPlaces, pid);
3820
- this.markDirty(pid);
3865
+ if (pid !== void 0) {
3866
+ this.cacheAddToken(pid, entry.token);
3867
+ setBit(this.markingBitmap, pid);
3868
+ this.markDirty(pid);
3869
+ } else {
3870
+ this.warnUnknownPlace(entry.place, t.name);
3871
+ }
3821
3872
  this.emitEvent({
3822
3873
  type: "token-added",
3823
3874
  timestamp: Date.now(),
@@ -3856,11 +3907,15 @@ var BitmapNetExecutor = class {
3856
3907
  for (let i = 0; i < len; i++) {
3857
3908
  const event = this.externalQueue[i];
3858
3909
  try {
3859
- const pid = this.compiled.placeId(event.place);
3860
- this.cacheAddToken(pid, event.token);
3910
+ const pid = this.compiled.tryPlaceId(event.place);
3861
3911
  this.marking.addToken(event.place, event.token);
3862
- setBit(this.markedPlaces, pid);
3863
- this.markDirty(pid);
3912
+ if (pid !== void 0) {
3913
+ this.cacheAddToken(pid, event.token);
3914
+ setBit(this.markingBitmap, pid);
3915
+ this.markDirty(pid);
3916
+ } else {
3917
+ this.warnUnknownPlace(event.place, "");
3918
+ }
3864
3919
  this.emitEvent({
3865
3920
  type: "token-added",
3866
3921
  timestamp: Date.now(),
@@ -3991,6 +4046,25 @@ var BitmapNetExecutor = class {
3991
4046
  this.wakeUp();
3992
4047
  }
3993
4048
  // ======================== Event Emission ========================
4049
+ /**
4050
+ * Reports an undeclared place once (CORE-072 AC4, emitted as the EVT-013
4051
+ * log-message event). `transitionName` is the producer, empty at the
4052
+ * initial-marking and injection seams. Retention never depends on this.
4053
+ */
4054
+ warnUnknownPlace(place2, transitionName) {
4055
+ if (this.warnedUnknownPlaces.has(place2.name)) return;
4056
+ this.warnedUnknownPlaces.add(place2.name);
4057
+ this.emitEvent({
4058
+ type: "log-message",
4059
+ timestamp: Date.now(),
4060
+ transitionName,
4061
+ logger: "libpetri.runtime",
4062
+ level: "WARN",
4063
+ message: `unknown place '${place2.name}': tokens are retained in the marking but inert (the net declares no arc on it)`,
4064
+ error: null,
4065
+ errorMessage: null
4066
+ });
4067
+ }
3994
4068
  emitEvent(event) {
3995
4069
  if (this.eventStoreEnabled) {
3996
4070
  try {
@@ -4028,6 +4102,12 @@ var PrecompiledNet = class _PrecompiledNet {
4028
4102
  // ==================== Opcode Programs ====================
4029
4103
  /** Per-transition consume opcode sequences. */
4030
4104
  consumeOps;
4105
+ /**
4106
+ * Per-transition index into {@link consumeOps} where the RESET tail begins; the
4107
+ * executor peeks read arcs at this boundary so reads observe the post-input,
4108
+ * pre-reset marking (EXEC-013 AC4).
4109
+ */
4110
+ resetOpsStart;
4031
4111
  /** Per-transition read-arc place IDs. */
4032
4112
  readOps;
4033
4113
  // ==================== Enablement Masks ====================
@@ -4066,11 +4146,10 @@ var PrecompiledNet = class _PrecompiledNet {
4066
4146
  // ==================== Reverse Index ====================
4067
4147
  placeToTransitions;
4068
4148
  consumptionPlaceIds;
4069
- // ==================== Cardinality & Guards ====================
4149
+ // ==================== Cardinality ====================
4070
4150
  cardinalityChecks;
4071
- hasGuards;
4072
4151
  // ν-net join flag (NU-020): gates the match-binding check off the hot
4073
- // enablement path for non-ν transitions, mirroring `hasGuards`.
4152
+ // enablement path for non-ν transitions.
4074
4153
  hasMatch;
4075
4154
  // ==================== Global Flags ====================
4076
4155
  allImmediate;
@@ -4147,9 +4226,12 @@ var PrecompiledNet = class _PrecompiledNet {
4147
4226
  this.inhibitorSparseMasks = inhibitorSparseMasks;
4148
4227
  const consumeOps = new Array(tc);
4149
4228
  const readOps = new Array(tc);
4229
+ this.resetOpsStart = new Uint32Array(tc);
4150
4230
  for (let tid = 0; tid < tc; tid++) {
4151
4231
  const t = compiled.transition(tid);
4152
- consumeOps[tid] = compileConsumeProgram(t, compiled);
4232
+ const program = compileConsumeProgram(t, compiled);
4233
+ consumeOps[tid] = program.ops;
4234
+ this.resetOpsStart[tid] = program.resetOpsStart;
4153
4235
  readOps[tid] = compileReadProgram(t, compiled);
4154
4236
  }
4155
4237
  this.consumeOps = consumeOps;
@@ -4165,15 +4247,12 @@ var PrecompiledNet = class _PrecompiledNet {
4165
4247
  this.placeToTransitions = placeToTransitions;
4166
4248
  this.consumptionPlaceIds = consumptionPlaceIds;
4167
4249
  const cardinalityChecks = new Array(tc);
4168
- const hasGuards = new Array(tc);
4169
4250
  const hasMatch = new Array(tc);
4170
4251
  for (let tid = 0; tid < tc; tid++) {
4171
4252
  cardinalityChecks[tid] = compiled.cardinalityCheck(tid);
4172
- hasGuards[tid] = compiled.hasGuards(tid);
4173
4253
  hasMatch[tid] = compiled.hasMatch(tid);
4174
4254
  }
4175
4255
  this.cardinalityChecks = cardinalityChecks;
4176
- this.hasGuards = hasGuards;
4177
4256
  this.hasMatch = hasMatch;
4178
4257
  this.earliestMs = new Float64Array(tc);
4179
4258
  this.latestMs = new Float64Array(tc);
@@ -4336,11 +4415,12 @@ function compileConsumeProgram(t, compiled) {
4336
4415
  break;
4337
4416
  }
4338
4417
  }
4418
+ const resetOpsStart = ops.length;
4339
4419
  for (const arc of t.resets) {
4340
4420
  const pid = compiled.placeId(arc.place);
4341
4421
  ops.push(RESET, pid);
4342
4422
  }
4343
- return ops;
4423
+ return { ops, resetOpsStart };
4344
4424
  }
4345
4425
  function compileReadProgram(t, compiled) {
4346
4426
  const pids = [];
@@ -4370,6 +4450,14 @@ var PrecompiledNetExecutor = class {
4370
4450
  // ==================== Token Storage ====================
4371
4451
  /** Per-place token arrays, indexed by pid. */
4372
4452
  tokenQueues;
4453
+ /**
4454
+ * Tokens on places the compiled net does not know (CORE-072 AC3). Retained —
4455
+ * never dropped — and merged into the materialized marking, matching the
4456
+ * BitmapNetExecutor reference, whose Marking keeps them naturally. Keyed by
4457
+ * place NAME (TS Place identity is name-based), carrying the Place so
4458
+ * materializing a Marking has one to hand.
4459
+ */
4460
+ unknownPlaceTokens = /* @__PURE__ */ new Map();
4373
4461
  /** Monotonic source for ν-name minting (ctx.freshName(), NU-010). */
4374
4462
  freshNameCounter = 0;
4375
4463
  /**
@@ -4455,7 +4543,13 @@ var PrecompiledNetExecutor = class {
4455
4543
  this.tokenQueues[pid] = [];
4456
4544
  }
4457
4545
  for (const [place2, tokens] of initialTokens) {
4458
- const pid = prog.compiled.placeId(place2);
4546
+ const pid = prog.compiled.tryPlaceId(place2);
4547
+ if (pid === void 0) {
4548
+ for (let i = 0; i < tokens.length; i++) {
4549
+ this.retainUnknownToken(place2, tokens[i], "");
4550
+ }
4551
+ continue;
4552
+ }
4459
4553
  const q = this.tokenQueues[pid];
4460
4554
  for (const token of tokens) {
4461
4555
  q.push(token);
@@ -4538,9 +4632,7 @@ var PrecompiledNetExecutor = class {
4538
4632
  for (let keyIdx = 0; keyIdx < ms.keys.length; keyIdx++) {
4539
4633
  const mk = ms.keys[keyIdx];
4540
4634
  const pid = prog.compiled.placeId(mk.place);
4541
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
4542
4635
  for (const token of this.tokenQueues[pid]) {
4543
- if (guard && !guard(token.value)) continue;
4544
4636
  const name = mk.key(token.value);
4545
4637
  if (name !== void 0 && name !== null) matcher.add(keyIdx, name, token.createdAt);
4546
4638
  }
@@ -4559,8 +4651,6 @@ var PrecompiledNetExecutor = class {
4559
4651
  if (cache == null) continue;
4560
4652
  const t = prog.compiled.transition(tid);
4561
4653
  const mk = t.matchSpec.keys[keyIdx];
4562
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
4563
- if (guard && !guard(token.value)) continue;
4564
4654
  const name = mk.key(token.value);
4565
4655
  if (name !== void 0 && name !== null) cache.add(keyIdx, name, token.createdAt);
4566
4656
  }
@@ -4772,17 +4862,6 @@ var PrecompiledNetExecutor = class {
4772
4862
  if (this.tokenQueues[pid].length < cardCheck.requiredCounts[i]) return false;
4773
4863
  }
4774
4864
  }
4775
- if (prog.hasGuards[tid]) {
4776
- const t = prog.compiled.transition(tid);
4777
- const cache = this.matchCaches[tid];
4778
- const ms = t.matchSpec;
4779
- for (const spec of t.inputSpecs) {
4780
- if (!spec.guard) continue;
4781
- if (cache != null && ms && keyForPlace(ms, spec.place.name) !== void 0) continue;
4782
- const required = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
4783
- if (this.countMatching(prog.compiled.placeId(spec.place), spec.guard) < required) return false;
4784
- }
4785
- }
4786
4865
  if (prog.hasMatch[tid]) {
4787
4866
  const cache = this.matchCaches[tid];
4788
4867
  const noBinding = cache != null ? cache.best() === null : findBinding(prog.compiled.transition(tid), (p) => this.tokenQueues[prog.compiled.placeId(p)]) === null;
@@ -4792,18 +4871,18 @@ var PrecompiledNetExecutor = class {
4792
4871
  }
4793
4872
  return true;
4794
4873
  }
4795
- countMatching(pid, guard) {
4874
+ countMatching(pid, predicate) {
4796
4875
  const q = this.tokenQueues[pid];
4797
4876
  let matching = 0;
4798
4877
  for (let i = 0; i < q.length; i++) {
4799
- if (guard(q[i].value)) matching++;
4878
+ if (predicate(q[i].value)) matching++;
4800
4879
  }
4801
4880
  return matching;
4802
4881
  }
4803
- removeFirstMatching(pid, guard) {
4882
+ removeFirstMatching(pid, predicate) {
4804
4883
  const q = this.tokenQueues[pid];
4805
4884
  for (let i = 0; i < q.length; i++) {
4806
- if (guard(q[i].value)) {
4885
+ if (predicate(q[i].value)) {
4807
4886
  return i === 0 ? q.shift() : q.splice(i, 1)[0];
4808
4887
  }
4809
4888
  }
@@ -4882,7 +4961,7 @@ var PrecompiledNetExecutor = class {
4882
4961
  * EventStore.append on a token-removed or transition-started emit, or an error thrown while
4883
4962
  * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
4884
4963
  * The transition is instead failed and marked dirty for re-evaluation, the same treatment an
4885
- * asynchronously-reported failure gets. Enablement-phase throws (a guard or key function that
4964
+ * asynchronously-reported failure gets. Enablement-phase throws (a key function that
4886
4965
  * throws inside canEnable, before the firing) run outside this boundary and are not contained
4887
4966
  * here.
4888
4967
  *
@@ -4890,10 +4969,9 @@ var PrecompiledNetExecutor = class {
4890
4969
  * so a throw inside that window would leave bits asserting tokens that are gone; the
4891
4970
  * recovery re-runs updateBitmapAfterConsumption against the true queue lengths.
4892
4971
  *
4893
- * Named to avoid colliding with the pre-existing {@link fireTransitionGuarded}
4894
- * guard-consume helper. Unlike the Java runtime there is no VirtualMachineError/
4895
- * LinkageError analogue on the JS side, so every throw is contained here — there is
4896
- * deliberately no fatal-rethrow escape hatch.
4972
+ * Unlike the Java runtime there is no VirtualMachineError/LinkageError analogue on
4973
+ * the JS side, so every throw is contained here — there is deliberately no
4974
+ * fatal-rethrow escape hatch.
4897
4975
  */
4898
4976
  fireTransitionContained(tid) {
4899
4977
  try {
@@ -4935,12 +5013,11 @@ var PrecompiledNetExecutor = class {
4935
5013
  const inputs = new TokenInput();
4936
5014
  if (t.matchSpec) {
4937
5015
  this.fireTransitionMatched(tid, t, inputs, consumed);
4938
- } else if (prog.hasGuards[tid]) {
4939
- this.fireTransitionGuarded(tid, t, inputs, consumed);
4940
5016
  } else {
4941
5017
  const ops = prog.consumeOps[tid];
5018
+ const resetOpsStart = prog.resetOpsStart[tid];
4942
5019
  let pc = 0;
4943
- while (pc < ops.length) {
5020
+ while (pc < resetOpsStart) {
4944
5021
  const opcode = ops[pc++];
4945
5022
  switch (opcode) {
4946
5023
  case CONSUME_ONE: {
@@ -5010,32 +5087,30 @@ var PrecompiledNetExecutor = class {
5010
5087
  }
5011
5088
  break;
5012
5089
  }
5013
- case RESET: {
5014
- const pid = ops[pc++];
5015
- const place2 = prog.places[pid];
5016
- const tokens = this.tokenQueues[pid].splice(0);
5017
- this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
5018
- this.hasPendingResets = true;
5019
- for (const token of tokens) {
5020
- consumed.push(token);
5021
- this.emitEvent({
5022
- type: "token-removed",
5023
- timestamp: Date.now(),
5024
- placeName: place2.name,
5025
- token
5026
- });
5027
- }
5028
- break;
5029
- }
5090
+ default:
5091
+ throw new Error(`Unknown opcode: ${opcode}`);
5030
5092
  }
5031
5093
  }
5032
- }
5033
- const readPids = prog.readOps[tid];
5034
- for (let i = 0; i < readPids.length; i++) {
5035
- const pid = readPids[i];
5036
- const q = this.tokenQueues[pid];
5037
- if (q.length > 0) {
5038
- inputs.add(prog.places[pid], q[0]);
5094
+ this.peekReadArcs(tid, inputs);
5095
+ while (pc < ops.length) {
5096
+ const opcode = ops[pc++];
5097
+ if (opcode !== RESET) {
5098
+ throw new Error(`Unknown opcode: ${opcode} (expected RESET past resetOpsStart)`);
5099
+ }
5100
+ const pid = ops[pc++];
5101
+ const place2 = prog.places[pid];
5102
+ const tokens = this.tokenQueues[pid].splice(0);
5103
+ this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
5104
+ this.hasPendingResets = true;
5105
+ for (const token of tokens) {
5106
+ consumed.push(token);
5107
+ this.emitEvent({
5108
+ type: "token-removed",
5109
+ timestamp: Date.now(),
5110
+ placeName: place2.name,
5111
+ token
5112
+ });
5113
+ }
5039
5114
  }
5040
5115
  }
5041
5116
  this.updateBitmapAfterConsumption(tid);
@@ -5132,9 +5207,8 @@ var PrecompiledNetExecutor = class {
5132
5207
  }
5133
5208
  /**
5134
5209
  * Consumes the name-matched tokens for a ν-net join (NU-020): correlated
5135
- * inputs take tokens whose projected name equals the chosen binding (guard
5136
- * first, then name equality — NU-021); other inputs consume FIFO. Reset arcs
5137
- * are honoured as on the opcode path. Mirrors {@link fireTransitionGuarded}.
5210
+ * inputs take tokens whose projected name equals the chosen binding (NU-021);
5211
+ * other inputs consume FIFO. Reset arcs are honoured as on the opcode path.
5138
5212
  */
5139
5213
  fireTransitionMatched(tid, t, inputs, consumed) {
5140
5214
  const prog = this.program;
@@ -5145,8 +5219,7 @@ var PrecompiledNetExecutor = class {
5145
5219
  for (const inSpec of t.inputSpecs) {
5146
5220
  const pid = prog.compiled.placeId(inSpec.place);
5147
5221
  const keyFn = keyForPlace(ms, inSpec.place.name);
5148
- const baseGuard = inSpec.guard;
5149
- const pred = keyFn && chosen !== null ? (v) => (baseGuard ? baseGuard(v) : true) && keyFn(v) === chosen : baseGuard;
5222
+ const pred = keyFn && chosen !== null ? (v) => keyFn(v) === chosen : void 0;
5150
5223
  let toConsume;
5151
5224
  switch (inSpec.type) {
5152
5225
  case "one":
@@ -5173,6 +5246,7 @@ var PrecompiledNetExecutor = class {
5173
5246
  });
5174
5247
  }
5175
5248
  }
5249
+ this.peekReadArcs(tid, inputs);
5176
5250
  for (const arc of t.resets) {
5177
5251
  const pid = prog.compiled.placeId(arc.place);
5178
5252
  const tokens = this.tokenQueues[pid].splice(0);
@@ -5189,52 +5263,19 @@ var PrecompiledNetExecutor = class {
5189
5263
  }
5190
5264
  }
5191
5265
  }
5192
- fireTransitionGuarded(_tid, t, inputs, consumed) {
5266
+ /**
5267
+ * Peeks each read-arc place's front token into the context inputs. Called at
5268
+ * the input/reset boundary of a firing (EXEC-013): after input consumption,
5269
+ * before reset draining — so read(p)+reset(p) observes the pre-reset token.
5270
+ */
5271
+ peekReadArcs(tid, inputs) {
5193
5272
  const prog = this.program;
5194
- for (const inSpec of t.inputSpecs) {
5195
- const pid = prog.compiled.placeId(inSpec.place);
5196
- let toConsume;
5197
- switch (inSpec.type) {
5198
- case "one":
5199
- toConsume = 1;
5200
- break;
5201
- case "exactly":
5202
- toConsume = inSpec.count;
5203
- break;
5204
- case "all":
5205
- toConsume = inSpec.guard ? this.countMatching(pid, inSpec.guard) : this.tokenQueues[pid].length;
5206
- break;
5207
- case "at-least":
5208
- toConsume = inSpec.guard ? this.countMatching(pid, inSpec.guard) : this.tokenQueues[pid].length;
5209
- break;
5210
- }
5211
- const guardFn = inSpec.guard;
5212
- for (let i = 0; i < toConsume; i++) {
5213
- const token = guardFn ? this.removeFirstMatching(pid, guardFn) : this.tokenQueues[pid].shift() ?? null;
5214
- if (token === null) break;
5215
- consumed.push(token);
5216
- inputs.add(inSpec.place, token);
5217
- this.emitEvent({
5218
- type: "token-removed",
5219
- timestamp: Date.now(),
5220
- placeName: inSpec.place.name,
5221
- token
5222
- });
5223
- }
5224
- }
5225
- for (const arc of t.resets) {
5226
- const pid = prog.compiled.placeId(arc.place);
5227
- const tokens = this.tokenQueues[pid].splice(0);
5228
- this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
5229
- this.hasPendingResets = true;
5230
- for (const token of tokens) {
5231
- consumed.push(token);
5232
- this.emitEvent({
5233
- type: "token-removed",
5234
- timestamp: Date.now(),
5235
- placeName: arc.place.name,
5236
- token
5237
- });
5273
+ const readPids = prog.readOps[tid];
5274
+ for (let i = 0; i < readPids.length; i++) {
5275
+ const pid = readPids[i];
5276
+ const q = this.tokenQueues[pid];
5277
+ if (q.length > 0) {
5278
+ inputs.add(prog.places[pid], q[0]);
5238
5279
  }
5239
5280
  }
5240
5281
  }
@@ -5302,12 +5343,8 @@ var PrecompiledNetExecutor = class {
5302
5343
  }
5303
5344
  const produced = [];
5304
5345
  for (const entry of outputs.entries()) {
5305
- const pid = prog.compiled.placeId(entry.place);
5306
- this.cacheAddToken(pid, entry.token);
5307
- this.tokenQueues[pid].push(entry.token);
5346
+ this.produceToken(entry.place, entry.token, t.name);
5308
5347
  produced.push(entry.token);
5309
- this.setMarkingBit(pid);
5310
- this.markDirty(pid);
5311
5348
  this.emitEvent({
5312
5349
  type: "token-added",
5313
5350
  timestamp: Date.now(),
@@ -5342,16 +5379,11 @@ var PrecompiledNetExecutor = class {
5342
5379
  processExternalEvents() {
5343
5380
  if (this.externalQueue.length === 0) return;
5344
5381
  if (this.closed) return;
5345
- const prog = this.program;
5346
5382
  const len = this.externalQueue.length;
5347
5383
  for (let i = 0; i < len; i++) {
5348
5384
  const event = this.externalQueue[i];
5349
5385
  try {
5350
- const pid = prog.compiled.placeId(event.place);
5351
- this.cacheAddToken(pid, event.token);
5352
- this.tokenQueues[pid].push(event.token);
5353
- this.setMarkingBit(pid);
5354
- this.markDirty(pid);
5386
+ this.produceToken(event.place, event.token, "");
5355
5387
  this.emitEvent({
5356
5388
  type: "token-added",
5357
5389
  timestamp: Date.now(),
@@ -5365,6 +5397,45 @@ var PrecompiledNetExecutor = class {
5365
5397
  }
5366
5398
  this.externalQueue.length = 0;
5367
5399
  }
5400
+ /**
5401
+ * Adds a produced or injected token: queue/bitmap/dirty for compiled places,
5402
+ * or the retention side map when the program does not know it (CORE-072 AC3).
5403
+ * `transitionName` is the producer, empty at the injection seam.
5404
+ */
5405
+ produceToken(place2, token, transitionName) {
5406
+ const pid = this.program.compiled.tryPlaceId(place2);
5407
+ if (pid === void 0) {
5408
+ this.retainUnknownToken(place2, token, transitionName);
5409
+ return;
5410
+ }
5411
+ this.cacheAddToken(pid, token);
5412
+ this.tokenQueues[pid].push(token);
5413
+ this.setMarkingBit(pid);
5414
+ this.markDirty(pid);
5415
+ }
5416
+ /**
5417
+ * Retains a token on an undeclared place (CORE-072 AC3) and reports the place
5418
+ * once — the first insert is the only one that creates a map entry, so a hot
5419
+ * loop cannot flood (AC4, emitted as the EVT-013 log-message event).
5420
+ */
5421
+ retainUnknownToken(place2, token, transitionName) {
5422
+ const retained = this.unknownPlaceTokens.get(place2.name);
5423
+ if (retained !== void 0) {
5424
+ retained.tokens.push(token);
5425
+ return;
5426
+ }
5427
+ this.unknownPlaceTokens.set(place2.name, { place: place2, tokens: [token] });
5428
+ this.emitEvent({
5429
+ type: "log-message",
5430
+ timestamp: Date.now(),
5431
+ transitionName,
5432
+ logger: "libpetri.runtime",
5433
+ level: "WARN",
5434
+ message: `unknown place '${place2.name}': tokens are retained in the marking but inert (the net declares no arc on it)`,
5435
+ error: null,
5436
+ errorMessage: null
5437
+ });
5438
+ }
5368
5439
  drainPendingExternalEvents() {
5369
5440
  while (this.externalQueue.length > 0) {
5370
5441
  this.externalQueue.shift().resolve(false);
@@ -5448,6 +5519,11 @@ var PrecompiledNetExecutor = class {
5448
5519
  m.addToken(place2, q[i]);
5449
5520
  }
5450
5521
  }
5522
+ for (const { place: place2, tokens } of this.unknownPlaceTokens.values()) {
5523
+ for (const token of tokens) {
5524
+ m.addToken(place2, token);
5525
+ }
5526
+ }
5451
5527
  this.marking = m;
5452
5528
  return m;
5453
5529
  }
@@ -5545,7 +5621,6 @@ export {
5545
5621
  fork,
5546
5622
  forwardInput,
5547
5623
  hasDeadline,
5548
- hasGuard,
5549
5624
  immediate,
5550
5625
  inMemoryEventStore,
5551
5626
  inhibitorArc,
@@ -5559,7 +5634,6 @@ export {
5559
5634
  matchCorrelates,
5560
5635
  matchKey,
5561
5636
  matchSpec,
5562
- matchesGuard,
5563
5637
  nameId,
5564
5638
  noopEventStore,
5565
5639
  one,