libpetri 2.12.1 → 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,33 @@
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,
11
+ fork,
7
12
  forwardInput,
13
+ isPassthrough,
8
14
  isProven,
9
15
  isViolated,
16
+ one,
10
17
  outPlace,
18
+ passthrough,
19
+ produce,
20
+ requireOutputProducingActions,
21
+ requiredCount,
11
22
  timeout,
12
23
  timeoutPlace,
24
+ transform,
25
+ transformAsync,
26
+ transformFrom,
27
+ withTimeout,
13
28
  xor,
14
29
  xorPlaces
15
- } from "./chunk-V3WTQRHC.js";
30
+ } from "./chunk-5W6SVYPD.js";
16
31
  import {
17
32
  eventTransitionName,
18
33
  isFailureEvent
@@ -22,7 +37,7 @@ import {
22
37
  matchCorrelates,
23
38
  matchKey,
24
39
  matchSpec
25
- } from "./chunk-E3ZWB645.js";
40
+ } from "./chunk-YVIPJ6KM.js";
26
41
  import {
27
42
  MAX_DURATION_MS,
28
43
  deadline,
@@ -62,8 +77,8 @@ function environmentPlace(name) {
62
77
  }
63
78
 
64
79
  // src/core/arc.ts
65
- function inputArc(place2, guard) {
66
- return guard !== void 0 ? { type: "input", place: place2, guard } : { type: "input", place: place2 };
80
+ function inputArc(place2) {
81
+ return { type: "input", place: place2 };
67
82
  }
68
83
  function outputArc(place2) {
69
84
  return { type: "output", place: place2 };
@@ -80,139 +95,12 @@ function resetArc(place2) {
80
95
  function arcPlace(arc) {
81
96
  return arc.place;
82
97
  }
83
- function hasGuard(arc) {
84
- return arc.guard !== void 0;
85
- }
86
- function matchesGuard(arc, value) {
87
- if (arc.guard === void 0) return true;
88
- return arc.guard(value);
89
- }
90
-
91
- // src/core/in.ts
92
- function one(place2, guard) {
93
- return guard !== void 0 ? { type: "one", place: place2, guard } : { type: "one", place: place2 };
94
- }
95
- function exactly(count, place2, guard) {
96
- if (count < 1) {
97
- throw new Error(`count must be >= 1, got: ${count}`);
98
- }
99
- return guard !== void 0 ? { type: "exactly", place: place2, count, guard } : { type: "exactly", place: place2, count };
100
- }
101
- function all(place2, guard) {
102
- return guard !== void 0 ? { type: "all", place: place2, guard } : { type: "all", place: place2 };
103
- }
104
- function atLeast(minimum, place2, guard) {
105
- if (minimum < 1) {
106
- throw new Error(`minimum must be >= 1, got: ${minimum}`);
107
- }
108
- return guard !== void 0 ? { type: "at-least", place: place2, minimum, guard } : { type: "at-least", place: place2, minimum };
109
- }
110
- function requiredCount(spec) {
111
- switch (spec.type) {
112
- case "one":
113
- return 1;
114
- case "exactly":
115
- return spec.count;
116
- case "all":
117
- return 1;
118
- case "at-least":
119
- return spec.minimum;
120
- }
121
- }
122
- function consumptionCount(spec, available) {
123
- if (available < requiredCount(spec)) {
124
- throw new Error(
125
- `Cannot consume from '${spec.place.name}': available=${available}, required=${requiredCount(spec)}`
126
- );
127
- }
128
- switch (spec.type) {
129
- case "one":
130
- return 1;
131
- case "exactly":
132
- return spec.count;
133
- case "all":
134
- return available;
135
- case "at-least":
136
- return available;
137
- }
138
- }
139
98
 
140
99
  // src/core/name.ts
141
100
  function nameId(value) {
142
101
  return value;
143
102
  }
144
103
 
145
- // src/core/transition-action.ts
146
- function passthrough() {
147
- return PASSTHROUGH;
148
- }
149
- var PASSTHROUGH = async () => {
150
- };
151
- function transform(fn) {
152
- return async (ctx) => {
153
- const result = fn(ctx);
154
- for (const outputPlace of ctx.outputPlaces()) {
155
- ctx.output(outputPlace, result);
156
- }
157
- };
158
- }
159
- function fork() {
160
- return transform((ctx) => {
161
- const inputPlaces = ctx.inputPlaces();
162
- if (inputPlaces.size !== 1) {
163
- throw new Error(`Fork requires exactly 1 input place, found ${inputPlaces.size}`);
164
- }
165
- const inputPlace = inputPlaces.values().next().value;
166
- return ctx.input(inputPlace);
167
- });
168
- }
169
- function transformFrom(inputPlace, fn) {
170
- return transform((ctx) => fn(ctx.input(inputPlace)));
171
- }
172
- function transformAsync(fn) {
173
- return async (ctx) => {
174
- const result = await fn(ctx);
175
- for (const outputPlace of ctx.outputPlaces()) {
176
- ctx.output(outputPlace, result);
177
- }
178
- };
179
- }
180
- function produce(place2, value) {
181
- return async (ctx) => {
182
- ctx.output(place2, value);
183
- };
184
- }
185
- function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
186
- return (ctx) => {
187
- return new Promise((resolve2, reject) => {
188
- let completed = false;
189
- const timer = setTimeout(() => {
190
- if (!completed) {
191
- completed = true;
192
- ctx.output(timeoutPlace2, timeoutValue);
193
- resolve2();
194
- }
195
- }, timeoutMs);
196
- action(ctx).then(
197
- () => {
198
- if (!completed) {
199
- completed = true;
200
- clearTimeout(timer);
201
- resolve2();
202
- }
203
- },
204
- (err) => {
205
- if (!completed) {
206
- completed = true;
207
- clearTimeout(timer);
208
- reject(err);
209
- }
210
- }
211
- );
212
- });
213
- };
214
- }
215
-
216
104
  // src/core/token-output.ts
217
105
  var TokenOutput = class {
218
106
  _entries = [];
@@ -1063,7 +951,7 @@ function rewriteTransition(t, prefix, placeRemap) {
1063
951
  function substitutePlaces(t, remap) {
1064
952
  return rebuildWithName(t, t.name, remap);
1065
953
  }
1066
- function rebuildWithName(t, name, remap) {
954
+ function rebuildWithName(t, name, remap, normalizeInputs) {
1067
955
  const builder = Transition.builder(name).timing(t.timing).priority(t.priority).action(t.action);
1068
956
  const alias = buildPlaceAlias(t, remap);
1069
957
  if (alias.size > 0) {
@@ -1074,7 +962,7 @@ function rebuildWithName(t, name, remap) {
1074
962
  for (let i = 0; i < t.inputSpecs.length; i++) {
1075
963
  rewrittenInputs[i] = rewriteIn(t.inputSpecs[i], remap);
1076
964
  }
1077
- builder.inputs(...rewrittenInputs);
965
+ builder.inputs(...normalizeInputs !== void 0 ? normalizeInputs(rewrittenInputs) : rewrittenInputs);
1078
966
  }
1079
967
  if (t.outputSpec !== null) {
1080
968
  builder.outputs(rewriteOut(t.outputSpec, remap));
@@ -1134,13 +1022,13 @@ var EMPTY_ALIAS2 = /* @__PURE__ */ new Map();
1134
1022
  function rewriteIn(spec, remap) {
1135
1023
  switch (spec.type) {
1136
1024
  case "one":
1137
- return one(resolve(spec.place, remap), spec.guard);
1025
+ return one(resolve(spec.place, remap));
1138
1026
  case "exactly":
1139
- return exactly(spec.count, resolve(spec.place, remap), spec.guard);
1027
+ return exactly(spec.count, resolve(spec.place, remap));
1140
1028
  case "all":
1141
- return all(resolve(spec.place, remap), spec.guard);
1029
+ return all(resolve(spec.place, remap));
1142
1030
  case "at-least":
1143
- return atLeast(spec.minimum, resolve(spec.place, remap), spec.guard);
1031
+ return atLeast(spec.minimum, resolve(spec.place, remap));
1144
1032
  }
1145
1033
  }
1146
1034
  function rewriteOut(out, remap) {
@@ -1195,7 +1083,11 @@ function mergeTransitions(caller, instance, mergedName) {
1195
1083
  if (mergedAction !== void 0) {
1196
1084
  builder.action(mergedAction);
1197
1085
  }
1198
- 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
+ );
1199
1091
  if (unionedInputs.length > 0) {
1200
1092
  builder.inputs(...unionedInputs);
1201
1093
  }
@@ -1326,48 +1218,128 @@ function describeTiming(t) {
1326
1218
  return `Exact(atMs=${t.atMs})`;
1327
1219
  }
1328
1220
  }
1329
- var PASSTHROUGH_REF = passthrough();
1330
- function isPassthrough(action) {
1331
- return action === PASSTHROUGH_REF;
1221
+ function keyOfInhibitor(arc) {
1222
+ return `inh|${arc.place.name}`;
1332
1223
  }
1333
- function keyOfIn(arc) {
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) {
1334
1276
  switch (arc.type) {
1335
1277
  case "one":
1336
- return arc.guard === void 0 ? `one|${arc.place.name}` : `one|${arc.place.name}|g:${guardKey(arc.guard)}`;
1278
+ return "one()";
1337
1279
  case "exactly":
1338
- 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})`;
1339
1281
  case "all":
1340
- return arc.guard === void 0 ? `all|${arc.place.name}` : `all|${arc.place.name}|g:${guardKey(arc.guard)}`;
1282
+ return "all()";
1341
1283
  case "at-least":
1342
- 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})`;
1343
1285
  }
1344
1286
  }
1345
- function keyOfInhibitor(arc) {
1346
- 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
+ }
1347
1298
  }
1348
- function keyOfRead(arc) {
1349
- 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;
1350
1314
  }
1351
- function keyOfReset(arc) {
1352
- return `reset|${arc.place.name}`;
1315
+ function describeKinds(kinds) {
1316
+ return `[${[...kinds].sort().join(", ")}]`;
1353
1317
  }
1354
- var GUARD_KEYS = /* @__PURE__ */ new WeakMap();
1355
- var guardKeyCounter = 0;
1356
- function guardKey(g) {
1357
- let k = GUARD_KEYS.get(g);
1358
- if (k === void 0) {
1359
- k = String(++guardKeyCounter);
1360
- GUARD_KEYS.set(g, k);
1361
- }
1362
- 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;
1363
1322
  }
1364
- function applyFusion(transitions, fusionMap) {
1323
+ function applyFusion(transitions, fusionMap, seamOf) {
1324
+ const normalizeInputs = (inputs) => normalizeInputArcs(inputs, seamOf);
1365
1325
  const rewritten = /* @__PURE__ */ new Set();
1366
1326
  for (const t of transitions) {
1367
- rewritten.add(substitutePlaces(t, fusionMap));
1327
+ rewritten.add(rebuildWithName(
1328
+ t,
1329
+ t.name,
1330
+ fusionMap,
1331
+ fusionTouchesInputs(t, fusionMap) ? normalizeInputs : void 0
1332
+ ));
1368
1333
  }
1369
1334
  return rewritten;
1370
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
+ }
1371
1343
 
1372
1344
  // src/verification/verification-harness.ts
1373
1345
  function buildVerificationResult(syntheticNet, perProperty) {
@@ -1572,6 +1544,7 @@ var SubnetDef = class _SubnetDef {
1572
1544
  }
1573
1545
  }
1574
1546
  const syntheticNet = PetriNet.builder("verify_" + this.name).compose(sut, portMappings).build();
1547
+ requireOutputProducingActions(syntheticNet);
1575
1548
  const perProperty = /* @__PURE__ */ new Map();
1576
1549
  for (const property of properties) {
1577
1550
  const verifier = SmtVerifier.forNet(syntheticNet).property(property);
@@ -1923,6 +1896,10 @@ var PetriNet = class _PetriNet {
1923
1896
  }
1924
1897
  /**
1925
1898
  * Creates a new PetriNet with actions bound via a resolver function.
1899
+ *
1900
+ * The resolver is called once per transition with its name. Returning `null`
1901
+ * defers that transition — it keeps whatever action it already carries — which
1902
+ * is what makes staged binding (**MOD-024** AC7) work.
1926
1903
  */
1927
1904
  bindActionsWithResolver(actionResolver) {
1928
1905
  const boundTransitions = /* @__PURE__ */ new Set();
@@ -2258,7 +2235,8 @@ var PetriNetBuilder = class _PetriNetBuilder {
2258
2235
  * Map<string, Place<unknown>> convention — TypeScript Place identity is
2259
2236
  * name-based per `runtime/compiled-net.ts`).
2260
2237
  * 3. Walk every transition through {@link applyFusion} to rewrite arc place
2261
- * references.
2238
+ * references; input arcs colliding on a canonical place merge per
2239
+ * [MOD-021] (additive where summable, rejected otherwise).
2262
2240
  * 4. Re-derive the place set from the rewritten transitions plus any
2263
2241
  * caller-declared standalone places, dropping non-canonical members.
2264
2242
  * Caller-declared standalone places that happen to be non-canonical
@@ -2344,7 +2322,10 @@ var PetriNetBuilder = class _PetriNetBuilder {
2344
2322
  nonCanonicalNames.add(nc.name);
2345
2323
  }
2346
2324
  }
2347
- 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
+ });
2348
2329
  const rebuiltPlaces = /* @__PURE__ */ new Set();
2349
2330
  for (const p of this._places) {
2350
2331
  if (!nonCanonicalNames.has(p.name)) {
@@ -2445,21 +2426,21 @@ var Marking = class _Marking {
2445
2426
  return result;
2446
2427
  }
2447
2428
  /**
2448
- * Removes and returns the first token whose value satisfies the guard predicate.
2429
+ * Removes and returns the first token whose value satisfies the predicate.
2449
2430
  *
2450
- * Performs a linear scan of the place's FIFO queue. If no guard is provided,
2451
- * 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
2452
2433
  * tokens and splices the first match out of the queue (O(n) worst case).
2453
2434
  */
2454
2435
  removeFirstMatching(spec) {
2455
2436
  const queue = this.tokens.get(spec.place.name);
2456
2437
  if (!queue || queue.length === 0) return null;
2457
- if (!spec.guard) {
2438
+ if (!spec.predicate) {
2458
2439
  return queue.shift();
2459
2440
  }
2460
2441
  for (let i = 0; i < queue.length; i++) {
2461
2442
  const token = queue[i];
2462
- if (spec.guard(token.value)) {
2443
+ if (spec.predicate(token.value)) {
2463
2444
  if (i === 0) queue.shift();
2464
2445
  else queue.splice(i, 1);
2465
2446
  return token;
@@ -2468,27 +2449,27 @@ var Marking = class _Marking {
2468
2449
  return null;
2469
2450
  }
2470
2451
  // ======================== Token Inspection ========================
2471
- /** Check if any token matches a guard predicate. */
2452
+ /** Check if any token satisfies the predicate. */
2472
2453
  hasMatchingToken(spec) {
2473
2454
  const queue = this.tokens.get(spec.place.name);
2474
2455
  if (!queue || queue.length === 0) return false;
2475
- if (!spec.guard) return true;
2476
- return queue.some((t) => spec.guard(t.value));
2456
+ if (!spec.predicate) return true;
2457
+ return queue.some((t) => spec.predicate(t.value));
2477
2458
  }
2478
2459
  /**
2479
- * Counts tokens in a place whose values satisfy the guard predicate.
2460
+ * Counts tokens in a place whose values satisfy the predicate.
2480
2461
  *
2481
- * If no guard is provided, returns the total token count (O(1)).
2482
- * With a guard, performs a linear scan over all tokens (O(n)).
2483
- * 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.
2484
2465
  */
2485
2466
  countMatching(spec) {
2486
2467
  const queue = this.tokens.get(spec.place.name);
2487
2468
  if (!queue || queue.length === 0) return 0;
2488
- if (!spec.guard) return queue.length;
2469
+ if (!spec.predicate) return queue.length;
2489
2470
  let count = 0;
2490
2471
  for (const t of queue) {
2491
- if (spec.guard(t.value)) count++;
2472
+ if (spec.predicate(t.value)) count++;
2492
2473
  }
2493
2474
  return count;
2494
2475
  }
@@ -2547,15 +2528,15 @@ var CompiledNet = class _CompiledNet {
2547
2528
  _placeToTransitions;
2548
2529
  // Consumption place IDs per transition (input + reset places)
2549
2530
  _consumptionPlaceIds;
2550
- // Cardinality and guard flags
2531
+ // Cardinality flags
2551
2532
  _cardinalityChecks;
2552
- _hasGuards;
2553
2533
  // ν-net join flag: the transition carries a MatchSpec (NU-020). Precomputed
2554
2534
  // so the hot enablement loop can skip the match check on non-ν transitions
2555
- // (zero-cost gating), mirroring `_hasGuards`.
2535
+ // (zero-cost gating).
2556
2536
  _hasMatch;
2557
2537
  constructor(net) {
2558
2538
  this.net = net;
2539
+ requireOutputProducingActions(net);
2559
2540
  const allPlacesSet = /* @__PURE__ */ new Map();
2560
2541
  for (const t of net.transitions) {
2561
2542
  for (const spec of t.inputSpecs) allPlacesSet.set(spec.place.name, spec.place);
@@ -2584,7 +2565,6 @@ var CompiledNet = class _CompiledNet {
2584
2565
  this._inhibitorMask = new Array(this.transitionCount);
2585
2566
  this._consumptionPlaceIds = new Array(this.transitionCount);
2586
2567
  this._cardinalityChecks = new Array(this.transitionCount).fill(null);
2587
- this._hasGuards = new Array(this.transitionCount).fill(false);
2588
2568
  this._hasMatch = new Array(this.transitionCount).fill(false);
2589
2569
  const placeToTransitionsList = new Array(this.placeCount);
2590
2570
  for (let i = 0; i < this.placeCount; i++) {
@@ -2596,16 +2576,20 @@ var CompiledNet = class _CompiledNet {
2596
2576
  const needs = new Uint32Array(this.wordCount);
2597
2577
  const inhibitors = new Uint32Array(this.wordCount);
2598
2578
  let needsCardinality = false;
2579
+ const seenInputPlaces = /* @__PURE__ */ new Set();
2599
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);
2600
2587
  const pid = this._placeIndex.get(inSpec.place.name);
2601
2588
  setBit(needs, pid);
2602
2589
  placeToTransitionsList[pid].push(tid);
2603
2590
  if (inSpec.type !== "one") {
2604
2591
  needsCardinality = true;
2605
2592
  }
2606
- if (inSpec.guard) {
2607
- this._hasGuards[tid] = true;
2608
- }
2609
2593
  }
2610
2594
  if (needsCardinality) {
2611
2595
  const pids = [];
@@ -2657,6 +2641,14 @@ var CompiledNet = class _CompiledNet {
2657
2641
  if (id === void 0) throw new Error(`Unknown place: ${place2.name}`);
2658
2642
  return id;
2659
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
+ }
2660
2652
  transitionId(t) {
2661
2653
  const id = this._transitionIndex.get(t);
2662
2654
  if (id === void 0) throw new Error(`Unknown transition: ${t.name}`);
@@ -2671,9 +2663,6 @@ var CompiledNet = class _CompiledNet {
2671
2663
  cardinalityCheck(tid) {
2672
2664
  return this._cardinalityChecks[tid];
2673
2665
  }
2674
- hasGuards(tid) {
2675
- return this._hasGuards[tid];
2676
- }
2677
2666
  hasMatch(tid) {
2678
2667
  return this._hasMatch[tid];
2679
2668
  }
@@ -2685,8 +2674,9 @@ var CompiledNet = class _CompiledNet {
2685
2674
  * 2. **Inhibitor check**: verifies no inhibitor places have tokens
2686
2675
  * via `!intersects(snapshot, inhibitorMask)`.
2687
2676
  *
2688
- * This is a necessary but not sufficient condition — cardinality and guard checks
2689
- * 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.
2690
2680
  */
2691
2681
  canEnableBitmap(tid, markingSnapshot) {
2692
2682
  if (!containsAll(markingSnapshot, this._needsMask[tid])) return false;
@@ -2811,10 +2801,9 @@ function selectMatchName(perPlace, requireds) {
2811
2801
  }
2812
2802
  return bestName;
2813
2803
  }
2814
- function buildNameIndex(tokens, key, guard) {
2804
+ function buildNameIndex(tokens, key) {
2815
2805
  const index = /* @__PURE__ */ new Map();
2816
2806
  for (const token of tokens) {
2817
- if (guard && !guard(token.value)) continue;
2818
2807
  const name = key(token.value);
2819
2808
  if (name === void 0 || name === null) continue;
2820
2809
  const ts = token.createdAt;
@@ -2844,17 +2833,11 @@ function findBinding(t, getTokens) {
2844
2833
  const perPlace = [];
2845
2834
  const requireds = [];
2846
2835
  for (const k of ms.keys) {
2847
- perPlace.push(buildNameIndex(getTokens(k.place), k.key, guardFor(t, k.place.name)));
2836
+ perPlace.push(buildNameIndex(getTokens(k.place), k.key));
2848
2837
  requireds.push(requiredFor(t, k.place.name));
2849
2838
  }
2850
2839
  return selectMatchName(perPlace, requireds);
2851
2840
  }
2852
- function guardFor(t, placeName) {
2853
- for (const inSpec of t.inputSpecs) {
2854
- if (inSpec.place.name === placeName) return inSpec.guard;
2855
- }
2856
- return void 0;
2857
- }
2858
2841
  var MinQueue = class {
2859
2842
  inStack = [];
2860
2843
  inMin = [];
@@ -2950,7 +2933,7 @@ var IncrementalMatcher = class {
2950
2933
  heaped = false;
2951
2934
  heap = new ReadyHeap();
2952
2935
  ready = /* @__PURE__ */ new Map();
2953
- /** Add one (already guard-passing) token carrying `name` at `createdAt` to input `i`. */
2936
+ /** Add one token carrying `name` at `createdAt` to input `i`. */
2954
2937
  add(i, name, createdAt) {
2955
2938
  let q = this.ts[i].get(name);
2956
2939
  if (!q) {
@@ -3112,9 +3095,7 @@ function validateOutSpec(tName, spec, producedPlaceNames) {
3112
3095
  );
3113
3096
  }
3114
3097
  if (satisfied.length > 1) {
3115
- throw new OutViolationError(
3116
- `'${tName}': XOR violation - multiple branches produced`
3117
- );
3098
+ return resolveXorAmbiguity(tName, satisfied);
3118
3099
  }
3119
3100
  return satisfied[0];
3120
3101
  }
@@ -3122,14 +3103,26 @@ function validateOutSpec(tName, spec, producedPlaceNames) {
3122
3103
  return validateOutSpec(tName, spec.child, producedPlaceNames);
3123
3104
  }
3124
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
+ }
3125
3117
  function produceTimeoutOutput(context, timeoutChild) {
3126
3118
  switch (timeoutChild.type) {
3127
3119
  case "place":
3128
3120
  context.outputToHarvest(timeoutChild.place, null);
3129
3121
  break;
3130
3122
  case "forward-input": {
3131
- const value = context.input(timeoutChild.from);
3132
- context.outputToHarvest(timeoutChild.to, value);
3123
+ for (const value of context.inputs(timeoutChild.from)) {
3124
+ context.outputToHarvest(timeoutChild.to, value);
3125
+ }
3133
3126
  break;
3134
3127
  }
3135
3128
  case "and":
@@ -3170,7 +3163,7 @@ var BitmapNetExecutor = class {
3170
3163
  allSamePriority;
3171
3164
  eventStoreEnabled;
3172
3165
  // Bitmaps (Uint32Array, direct writes)
3173
- markedPlaces;
3166
+ markingBitmap;
3174
3167
  dirtySet;
3175
3168
  markingSnapBuffer;
3176
3169
  dirtySnapBuffer;
@@ -3199,6 +3192,11 @@ var BitmapNetExecutor = class {
3199
3192
  readyBuffer = [];
3200
3193
  // Pending reset places for clock-restart detection
3201
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();
3202
3200
  transitionInputPlaceNames;
3203
3201
  running = false;
3204
3202
  draining = false;
@@ -3218,7 +3216,7 @@ var BitmapNetExecutor = class {
3218
3216
  }
3219
3217
  this.startMs = performance.now();
3220
3218
  const wordCount = this.compiled.wordCount;
3221
- this.markedPlaces = new Uint32Array(wordCount);
3219
+ this.markingBitmap = new Uint32Array(wordCount);
3222
3220
  this.markingSnapBuffer = new Uint32Array(wordCount);
3223
3221
  this.firingSnapBuffer = new Uint32Array(wordCount);
3224
3222
  const dirtyWords = this.compiled.transitionCount + BIT_MASK >>> WORD_SHIFT;
@@ -3254,6 +3252,11 @@ var BitmapNetExecutor = class {
3254
3252
  for (const spec of t.inputSpecs) names.add(spec.place.name);
3255
3253
  this.transitionInputPlaceNames.set(t, names);
3256
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
+ }
3257
3260
  this.initMatchCaches();
3258
3261
  }
3259
3262
  /**
@@ -3312,9 +3315,7 @@ var BitmapNetExecutor = class {
3312
3315
  for (let keyIdx = 0; keyIdx < ms.keys.length; keyIdx++) {
3313
3316
  const mk = ms.keys[keyIdx];
3314
3317
  const pid = compiled.placeId(mk.place);
3315
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
3316
3318
  for (const token of this.marking.peekTokens(mk.place)) {
3317
- if (guard && !guard(token.value)) continue;
3318
3319
  const name = mk.key(token.value);
3319
3320
  if (name !== void 0 && name !== null) matcher.add(keyIdx, name, token.createdAt);
3320
3321
  }
@@ -3333,8 +3334,6 @@ var BitmapNetExecutor = class {
3333
3334
  if (cache == null) continue;
3334
3335
  const t = compiled.transition(tid);
3335
3336
  const mk = t.matchSpec.keys[keyIdx];
3336
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
3337
- if (guard && !guard(token.value)) continue;
3338
3337
  const name = mk.key(token.value);
3339
3338
  if (name !== void 0 && name !== null) cache.add(keyIdx, name, token.createdAt);
3340
3339
  }
@@ -3362,7 +3361,7 @@ var BitmapNetExecutor = class {
3362
3361
  netName: this.compiled.net.name,
3363
3362
  executionId: this.executionId()
3364
3363
  });
3365
- this.initializeMarkedBitmap();
3364
+ this.initializeMarkingBitmap();
3366
3365
  this.markAllDirty();
3367
3366
  this.emitEvent({
3368
3367
  type: "marking-snapshot",
@@ -3417,11 +3416,11 @@ var BitmapNetExecutor = class {
3417
3416
  return this.inject(envPlace, tokenOf(value));
3418
3417
  }
3419
3418
  // ======================== Initialize ========================
3420
- initializeMarkedBitmap() {
3419
+ initializeMarkingBitmap() {
3421
3420
  for (let pid = 0; pid < this.compiled.placeCount; pid++) {
3422
3421
  const place2 = this.compiled.place(pid);
3423
3422
  if (this.marking.hasTokens(place2)) {
3424
- setBit(this.markedPlaces, pid);
3423
+ setBit(this.markingBitmap, pid);
3425
3424
  }
3426
3425
  }
3427
3426
  }
@@ -3449,7 +3448,7 @@ var BitmapNetExecutor = class {
3449
3448
  updateDirtyTransitions() {
3450
3449
  const nowMs = performance.now();
3451
3450
  const markingSnap = this.markingSnapBuffer;
3452
- markingSnap.set(this.markedPlaces);
3451
+ markingSnap.set(this.markingBitmap);
3453
3452
  const dirtyWords = this.dirtySet.length;
3454
3453
  const dirtySnap = this.dirtySnapBuffer;
3455
3454
  for (let w = 0; w < dirtyWords; w++) {
@@ -3534,17 +3533,6 @@ var BitmapNetExecutor = class {
3534
3533
  if (this.marking.tokenCount(place2) < required) return false;
3535
3534
  }
3536
3535
  }
3537
- if (this.compiled.hasGuards(tid)) {
3538
- const t = this.compiled.transition(tid);
3539
- const cache = this.matchCaches[tid];
3540
- const ms = t.matchSpec;
3541
- for (const spec of t.inputSpecs) {
3542
- if (!spec.guard) continue;
3543
- if (cache != null && ms && keyForPlace(ms, spec.place.name) !== void 0) continue;
3544
- const requiredCount2 = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
3545
- if (this.marking.countMatching(spec) < requiredCount2) return false;
3546
- }
3547
- }
3548
3536
  if (this.compiled.hasMatch(tid)) {
3549
3537
  const cache = this.matchCaches[tid];
3550
3538
  const noBinding = cache != null ? cache.best() === null : findBinding(this.compiled.transition(tid), (p) => this.marking.peekTokens(p)) === null;
@@ -3575,7 +3563,7 @@ var BitmapNetExecutor = class {
3575
3563
  * Fast path for nets where all transitions are immediate and same priority.
3576
3564
  * Skips timing checks, sorting, and snapshot buffer — just scan and fire.
3577
3565
  *
3578
- * Uses live `markedPlaces` instead of a snapshot. Safe because
3566
+ * Uses live `markingBitmap` instead of a snapshot. Safe because
3579
3567
  * `updateBitmapAfterConsumption()` synchronously updates the bitmap before the next
3580
3568
  * iteration. For equal-priority immediate transitions, tid scan order satisfies
3581
3569
  * FIFO-by-enablement-time (all enabled in the same cycle).
@@ -3583,7 +3571,7 @@ var BitmapNetExecutor = class {
3583
3571
  fireReadyImmediate() {
3584
3572
  for (let tid = 0; tid < this.compiled.transitionCount; tid++) {
3585
3573
  if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
3586
- if (this.canEnable(tid, this.markedPlaces)) {
3574
+ if (this.canEnable(tid, this.markingBitmap)) {
3587
3575
  this.fireTransitionContained(tid);
3588
3576
  } else {
3589
3577
  this.enabledFlags[tid] = 0;
@@ -3612,12 +3600,12 @@ var BitmapNetExecutor = class {
3612
3600
  return a.enabledAtMs - b.enabledAtMs;
3613
3601
  });
3614
3602
  const freshSnap = this.firingSnapBuffer;
3615
- freshSnap.set(this.markedPlaces);
3603
+ freshSnap.set(this.markingBitmap);
3616
3604
  for (const entry of ready) {
3617
3605
  const { tid } = entry;
3618
3606
  if (this.enabledFlags[tid] && this.canEnable(tid, freshSnap)) {
3619
3607
  this.fireTransitionContained(tid);
3620
- freshSnap.set(this.markedPlaces);
3608
+ freshSnap.set(this.markingBitmap);
3621
3609
  } else {
3622
3610
  this.enabledFlags[tid] = 0;
3623
3611
  this.enabledTransitionCount--;
@@ -3633,7 +3621,7 @@ var BitmapNetExecutor = class {
3633
3621
  * EventStore.append on a token-removed or transition-started emit, or an error thrown while
3634
3622
  * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
3635
3623
  * 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
3624
+ * asynchronously-reported failure gets. Enablement-phase throws (a key function that
3637
3625
  * throws inside canEnable, before the firing) run outside this boundary and are not contained
3638
3626
  * here.
3639
3627
  *
@@ -3684,10 +3672,9 @@ var BitmapNetExecutor = class {
3684
3672
  const keyFn = ms ? keyForPlace(ms, inSpec.place.name) : void 0;
3685
3673
  let spec;
3686
3674
  if (keyFn && chosen !== null) {
3687
- const baseGuard = inSpec.guard;
3688
3675
  spec = {
3689
3676
  place: inSpec.place,
3690
- guard: (v) => (baseGuard ? baseGuard(v) : true) && keyFn(v) === chosen
3677
+ predicate: (v) => keyFn(v) === chosen
3691
3678
  };
3692
3679
  } else {
3693
3680
  spec = inSpec;
@@ -3702,11 +3689,11 @@ var BitmapNetExecutor = class {
3702
3689
  break;
3703
3690
  case "all":
3704
3691
  case "at-least":
3705
- 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);
3706
3693
  break;
3707
3694
  }
3708
3695
  for (let i = 0; i < toConsume; i++) {
3709
- 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);
3710
3697
  if (token === null) break;
3711
3698
  consumed.push(token);
3712
3699
  inputs.add(inSpec.place, token);
@@ -3830,7 +3817,7 @@ var BitmapNetExecutor = class {
3830
3817
  for (const pid of pids) {
3831
3818
  const place2 = this.compiled.place(pid);
3832
3819
  if (!this.marking.hasTokens(place2)) {
3833
- clearBit(this.markedPlaces, pid);
3820
+ clearBit(this.markingBitmap, pid);
3834
3821
  }
3835
3822
  this.markDirty(pid);
3836
3823
  }
@@ -3872,12 +3859,16 @@ var BitmapNetExecutor = class {
3872
3859
  }
3873
3860
  const produced = [];
3874
3861
  for (const entry of outputs.entries()) {
3875
- const pid = this.compiled.placeId(entry.place);
3876
- this.cacheAddToken(pid, entry.token);
3862
+ const pid = this.compiled.tryPlaceId(entry.place);
3877
3863
  this.marking.addToken(entry.place, entry.token);
3878
3864
  produced.push(entry.token);
3879
- setBit(this.markedPlaces, pid);
3880
- 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
+ }
3881
3872
  this.emitEvent({
3882
3873
  type: "token-added",
3883
3874
  timestamp: Date.now(),
@@ -3916,11 +3907,15 @@ var BitmapNetExecutor = class {
3916
3907
  for (let i = 0; i < len; i++) {
3917
3908
  const event = this.externalQueue[i];
3918
3909
  try {
3919
- const pid = this.compiled.placeId(event.place);
3920
- this.cacheAddToken(pid, event.token);
3910
+ const pid = this.compiled.tryPlaceId(event.place);
3921
3911
  this.marking.addToken(event.place, event.token);
3922
- setBit(this.markedPlaces, pid);
3923
- 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
+ }
3924
3919
  this.emitEvent({
3925
3920
  type: "token-added",
3926
3921
  timestamp: Date.now(),
@@ -4051,6 +4046,25 @@ var BitmapNetExecutor = class {
4051
4046
  this.wakeUp();
4052
4047
  }
4053
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
+ }
4054
4068
  emitEvent(event) {
4055
4069
  if (this.eventStoreEnabled) {
4056
4070
  try {
@@ -4088,6 +4102,12 @@ var PrecompiledNet = class _PrecompiledNet {
4088
4102
  // ==================== Opcode Programs ====================
4089
4103
  /** Per-transition consume opcode sequences. */
4090
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;
4091
4111
  /** Per-transition read-arc place IDs. */
4092
4112
  readOps;
4093
4113
  // ==================== Enablement Masks ====================
@@ -4126,11 +4146,10 @@ var PrecompiledNet = class _PrecompiledNet {
4126
4146
  // ==================== Reverse Index ====================
4127
4147
  placeToTransitions;
4128
4148
  consumptionPlaceIds;
4129
- // ==================== Cardinality & Guards ====================
4149
+ // ==================== Cardinality ====================
4130
4150
  cardinalityChecks;
4131
- hasGuards;
4132
4151
  // ν-net join flag (NU-020): gates the match-binding check off the hot
4133
- // enablement path for non-ν transitions, mirroring `hasGuards`.
4152
+ // enablement path for non-ν transitions.
4134
4153
  hasMatch;
4135
4154
  // ==================== Global Flags ====================
4136
4155
  allImmediate;
@@ -4207,9 +4226,12 @@ var PrecompiledNet = class _PrecompiledNet {
4207
4226
  this.inhibitorSparseMasks = inhibitorSparseMasks;
4208
4227
  const consumeOps = new Array(tc);
4209
4228
  const readOps = new Array(tc);
4229
+ this.resetOpsStart = new Uint32Array(tc);
4210
4230
  for (let tid = 0; tid < tc; tid++) {
4211
4231
  const t = compiled.transition(tid);
4212
- consumeOps[tid] = compileConsumeProgram(t, compiled);
4232
+ const program = compileConsumeProgram(t, compiled);
4233
+ consumeOps[tid] = program.ops;
4234
+ this.resetOpsStart[tid] = program.resetOpsStart;
4213
4235
  readOps[tid] = compileReadProgram(t, compiled);
4214
4236
  }
4215
4237
  this.consumeOps = consumeOps;
@@ -4225,15 +4247,12 @@ var PrecompiledNet = class _PrecompiledNet {
4225
4247
  this.placeToTransitions = placeToTransitions;
4226
4248
  this.consumptionPlaceIds = consumptionPlaceIds;
4227
4249
  const cardinalityChecks = new Array(tc);
4228
- const hasGuards = new Array(tc);
4229
4250
  const hasMatch = new Array(tc);
4230
4251
  for (let tid = 0; tid < tc; tid++) {
4231
4252
  cardinalityChecks[tid] = compiled.cardinalityCheck(tid);
4232
- hasGuards[tid] = compiled.hasGuards(tid);
4233
4253
  hasMatch[tid] = compiled.hasMatch(tid);
4234
4254
  }
4235
4255
  this.cardinalityChecks = cardinalityChecks;
4236
- this.hasGuards = hasGuards;
4237
4256
  this.hasMatch = hasMatch;
4238
4257
  this.earliestMs = new Float64Array(tc);
4239
4258
  this.latestMs = new Float64Array(tc);
@@ -4396,11 +4415,12 @@ function compileConsumeProgram(t, compiled) {
4396
4415
  break;
4397
4416
  }
4398
4417
  }
4418
+ const resetOpsStart = ops.length;
4399
4419
  for (const arc of t.resets) {
4400
4420
  const pid = compiled.placeId(arc.place);
4401
4421
  ops.push(RESET, pid);
4402
4422
  }
4403
- return ops;
4423
+ return { ops, resetOpsStart };
4404
4424
  }
4405
4425
  function compileReadProgram(t, compiled) {
4406
4426
  const pids = [];
@@ -4430,6 +4450,14 @@ var PrecompiledNetExecutor = class {
4430
4450
  // ==================== Token Storage ====================
4431
4451
  /** Per-place token arrays, indexed by pid. */
4432
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();
4433
4461
  /** Monotonic source for ν-name minting (ctx.freshName(), NU-010). */
4434
4462
  freshNameCounter = 0;
4435
4463
  /**
@@ -4515,7 +4543,13 @@ var PrecompiledNetExecutor = class {
4515
4543
  this.tokenQueues[pid] = [];
4516
4544
  }
4517
4545
  for (const [place2, tokens] of initialTokens) {
4518
- 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
+ }
4519
4553
  const q = this.tokenQueues[pid];
4520
4554
  for (const token of tokens) {
4521
4555
  q.push(token);
@@ -4598,9 +4632,7 @@ var PrecompiledNetExecutor = class {
4598
4632
  for (let keyIdx = 0; keyIdx < ms.keys.length; keyIdx++) {
4599
4633
  const mk = ms.keys[keyIdx];
4600
4634
  const pid = prog.compiled.placeId(mk.place);
4601
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
4602
4635
  for (const token of this.tokenQueues[pid]) {
4603
- if (guard && !guard(token.value)) continue;
4604
4636
  const name = mk.key(token.value);
4605
4637
  if (name !== void 0 && name !== null) matcher.add(keyIdx, name, token.createdAt);
4606
4638
  }
@@ -4619,8 +4651,6 @@ var PrecompiledNetExecutor = class {
4619
4651
  if (cache == null) continue;
4620
4652
  const t = prog.compiled.transition(tid);
4621
4653
  const mk = t.matchSpec.keys[keyIdx];
4622
- const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
4623
- if (guard && !guard(token.value)) continue;
4624
4654
  const name = mk.key(token.value);
4625
4655
  if (name !== void 0 && name !== null) cache.add(keyIdx, name, token.createdAt);
4626
4656
  }
@@ -4832,17 +4862,6 @@ var PrecompiledNetExecutor = class {
4832
4862
  if (this.tokenQueues[pid].length < cardCheck.requiredCounts[i]) return false;
4833
4863
  }
4834
4864
  }
4835
- if (prog.hasGuards[tid]) {
4836
- const t = prog.compiled.transition(tid);
4837
- const cache = this.matchCaches[tid];
4838
- const ms = t.matchSpec;
4839
- for (const spec of t.inputSpecs) {
4840
- if (!spec.guard) continue;
4841
- if (cache != null && ms && keyForPlace(ms, spec.place.name) !== void 0) continue;
4842
- const required = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
4843
- if (this.countMatching(prog.compiled.placeId(spec.place), spec.guard) < required) return false;
4844
- }
4845
- }
4846
4865
  if (prog.hasMatch[tid]) {
4847
4866
  const cache = this.matchCaches[tid];
4848
4867
  const noBinding = cache != null ? cache.best() === null : findBinding(prog.compiled.transition(tid), (p) => this.tokenQueues[prog.compiled.placeId(p)]) === null;
@@ -4852,18 +4871,18 @@ var PrecompiledNetExecutor = class {
4852
4871
  }
4853
4872
  return true;
4854
4873
  }
4855
- countMatching(pid, guard) {
4874
+ countMatching(pid, predicate) {
4856
4875
  const q = this.tokenQueues[pid];
4857
4876
  let matching = 0;
4858
4877
  for (let i = 0; i < q.length; i++) {
4859
- if (guard(q[i].value)) matching++;
4878
+ if (predicate(q[i].value)) matching++;
4860
4879
  }
4861
4880
  return matching;
4862
4881
  }
4863
- removeFirstMatching(pid, guard) {
4882
+ removeFirstMatching(pid, predicate) {
4864
4883
  const q = this.tokenQueues[pid];
4865
4884
  for (let i = 0; i < q.length; i++) {
4866
- if (guard(q[i].value)) {
4885
+ if (predicate(q[i].value)) {
4867
4886
  return i === 0 ? q.shift() : q.splice(i, 1)[0];
4868
4887
  }
4869
4888
  }
@@ -4942,7 +4961,7 @@ var PrecompiledNetExecutor = class {
4942
4961
  * EventStore.append on a token-removed or transition-started emit, or an error thrown while
4943
4962
  * consuming the matched tokens — unwinds out of the orchestrator loop and kills the executor.
4944
4963
  * 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
4964
+ * asynchronously-reported failure gets. Enablement-phase throws (a key function that
4946
4965
  * throws inside canEnable, before the firing) run outside this boundary and are not contained
4947
4966
  * here.
4948
4967
  *
@@ -4950,10 +4969,9 @@ var PrecompiledNetExecutor = class {
4950
4969
  * so a throw inside that window would leave bits asserting tokens that are gone; the
4951
4970
  * recovery re-runs updateBitmapAfterConsumption against the true queue lengths.
4952
4971
  *
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.
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.
4957
4975
  */
4958
4976
  fireTransitionContained(tid) {
4959
4977
  try {
@@ -4995,12 +5013,11 @@ var PrecompiledNetExecutor = class {
4995
5013
  const inputs = new TokenInput();
4996
5014
  if (t.matchSpec) {
4997
5015
  this.fireTransitionMatched(tid, t, inputs, consumed);
4998
- } else if (prog.hasGuards[tid]) {
4999
- this.fireTransitionGuarded(tid, t, inputs, consumed);
5000
5016
  } else {
5001
5017
  const ops = prog.consumeOps[tid];
5018
+ const resetOpsStart = prog.resetOpsStart[tid];
5002
5019
  let pc = 0;
5003
- while (pc < ops.length) {
5020
+ while (pc < resetOpsStart) {
5004
5021
  const opcode = ops[pc++];
5005
5022
  switch (opcode) {
5006
5023
  case CONSUME_ONE: {
@@ -5070,32 +5087,30 @@ var PrecompiledNetExecutor = class {
5070
5087
  }
5071
5088
  break;
5072
5089
  }
5073
- case RESET: {
5074
- const pid = ops[pc++];
5075
- const place2 = prog.places[pid];
5076
- const tokens = this.tokenQueues[pid].splice(0);
5077
- this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
5078
- this.hasPendingResets = true;
5079
- for (const token of tokens) {
5080
- consumed.push(token);
5081
- this.emitEvent({
5082
- type: "token-removed",
5083
- timestamp: Date.now(),
5084
- placeName: place2.name,
5085
- token
5086
- });
5087
- }
5088
- break;
5089
- }
5090
+ default:
5091
+ throw new Error(`Unknown opcode: ${opcode}`);
5090
5092
  }
5091
5093
  }
5092
- }
5093
- const readPids = prog.readOps[tid];
5094
- for (let i = 0; i < readPids.length; i++) {
5095
- const pid = readPids[i];
5096
- const q = this.tokenQueues[pid];
5097
- if (q.length > 0) {
5098
- 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
+ }
5099
5114
  }
5100
5115
  }
5101
5116
  this.updateBitmapAfterConsumption(tid);
@@ -5192,9 +5207,8 @@ var PrecompiledNetExecutor = class {
5192
5207
  }
5193
5208
  /**
5194
5209
  * Consumes the name-matched tokens for a ν-net join (NU-020): correlated
5195
- * inputs take tokens whose projected name equals the chosen binding (guard
5196
- * first, then name equality — NU-021); other inputs consume FIFO. Reset arcs
5197
- * 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.
5198
5212
  */
5199
5213
  fireTransitionMatched(tid, t, inputs, consumed) {
5200
5214
  const prog = this.program;
@@ -5205,8 +5219,7 @@ var PrecompiledNetExecutor = class {
5205
5219
  for (const inSpec of t.inputSpecs) {
5206
5220
  const pid = prog.compiled.placeId(inSpec.place);
5207
5221
  const keyFn = keyForPlace(ms, inSpec.place.name);
5208
- const baseGuard = inSpec.guard;
5209
- 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;
5210
5223
  let toConsume;
5211
5224
  switch (inSpec.type) {
5212
5225
  case "one":
@@ -5233,6 +5246,7 @@ var PrecompiledNetExecutor = class {
5233
5246
  });
5234
5247
  }
5235
5248
  }
5249
+ this.peekReadArcs(tid, inputs);
5236
5250
  for (const arc of t.resets) {
5237
5251
  const pid = prog.compiled.placeId(arc.place);
5238
5252
  const tokens = this.tokenQueues[pid].splice(0);
@@ -5249,52 +5263,19 @@ var PrecompiledNetExecutor = class {
5249
5263
  }
5250
5264
  }
5251
5265
  }
5252
- 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) {
5253
5272
  const prog = this.program;
5254
- for (const inSpec of t.inputSpecs) {
5255
- const pid = prog.compiled.placeId(inSpec.place);
5256
- let toConsume;
5257
- switch (inSpec.type) {
5258
- case "one":
5259
- toConsume = 1;
5260
- break;
5261
- case "exactly":
5262
- toConsume = inSpec.count;
5263
- break;
5264
- case "all":
5265
- toConsume = inSpec.guard ? this.countMatching(pid, inSpec.guard) : this.tokenQueues[pid].length;
5266
- break;
5267
- case "at-least":
5268
- toConsume = inSpec.guard ? this.countMatching(pid, inSpec.guard) : this.tokenQueues[pid].length;
5269
- break;
5270
- }
5271
- const guardFn = inSpec.guard;
5272
- for (let i = 0; i < toConsume; i++) {
5273
- const token = guardFn ? this.removeFirstMatching(pid, guardFn) : this.tokenQueues[pid].shift() ?? null;
5274
- if (token === null) break;
5275
- consumed.push(token);
5276
- inputs.add(inSpec.place, token);
5277
- this.emitEvent({
5278
- type: "token-removed",
5279
- timestamp: Date.now(),
5280
- placeName: inSpec.place.name,
5281
- token
5282
- });
5283
- }
5284
- }
5285
- for (const arc of t.resets) {
5286
- const pid = prog.compiled.placeId(arc.place);
5287
- const tokens = this.tokenQueues[pid].splice(0);
5288
- this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
5289
- this.hasPendingResets = true;
5290
- for (const token of tokens) {
5291
- consumed.push(token);
5292
- this.emitEvent({
5293
- type: "token-removed",
5294
- timestamp: Date.now(),
5295
- placeName: arc.place.name,
5296
- token
5297
- });
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]);
5298
5279
  }
5299
5280
  }
5300
5281
  }
@@ -5362,12 +5343,8 @@ var PrecompiledNetExecutor = class {
5362
5343
  }
5363
5344
  const produced = [];
5364
5345
  for (const entry of outputs.entries()) {
5365
- const pid = prog.compiled.placeId(entry.place);
5366
- this.cacheAddToken(pid, entry.token);
5367
- this.tokenQueues[pid].push(entry.token);
5346
+ this.produceToken(entry.place, entry.token, t.name);
5368
5347
  produced.push(entry.token);
5369
- this.setMarkingBit(pid);
5370
- this.markDirty(pid);
5371
5348
  this.emitEvent({
5372
5349
  type: "token-added",
5373
5350
  timestamp: Date.now(),
@@ -5402,16 +5379,11 @@ var PrecompiledNetExecutor = class {
5402
5379
  processExternalEvents() {
5403
5380
  if (this.externalQueue.length === 0) return;
5404
5381
  if (this.closed) return;
5405
- const prog = this.program;
5406
5382
  const len = this.externalQueue.length;
5407
5383
  for (let i = 0; i < len; i++) {
5408
5384
  const event = this.externalQueue[i];
5409
5385
  try {
5410
- const pid = prog.compiled.placeId(event.place);
5411
- this.cacheAddToken(pid, event.token);
5412
- this.tokenQueues[pid].push(event.token);
5413
- this.setMarkingBit(pid);
5414
- this.markDirty(pid);
5386
+ this.produceToken(event.place, event.token, "");
5415
5387
  this.emitEvent({
5416
5388
  type: "token-added",
5417
5389
  timestamp: Date.now(),
@@ -5425,6 +5397,45 @@ var PrecompiledNetExecutor = class {
5425
5397
  }
5426
5398
  this.externalQueue.length = 0;
5427
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
+ }
5428
5439
  drainPendingExternalEvents() {
5429
5440
  while (this.externalQueue.length > 0) {
5430
5441
  this.externalQueue.shift().resolve(false);
@@ -5508,6 +5519,11 @@ var PrecompiledNetExecutor = class {
5508
5519
  m.addToken(place2, q[i]);
5509
5520
  }
5510
5521
  }
5522
+ for (const { place: place2, tokens } of this.unknownPlaceTokens.values()) {
5523
+ for (const token of tokens) {
5524
+ m.addToken(place2, token);
5525
+ }
5526
+ }
5511
5527
  this.marking = m;
5512
5528
  return m;
5513
5529
  }
@@ -5605,20 +5621,19 @@ export {
5605
5621
  fork,
5606
5622
  forwardInput,
5607
5623
  hasDeadline,
5608
- hasGuard,
5609
5624
  immediate,
5610
5625
  inMemoryEventStore,
5611
5626
  inhibitorArc,
5612
5627
  inputArc,
5613
5628
  intersects,
5614
5629
  isFailureEvent,
5630
+ isPassthrough,
5615
5631
  isUnit,
5616
5632
  keyForPlace,
5617
5633
  latest,
5618
5634
  matchCorrelates,
5619
5635
  matchKey,
5620
5636
  matchSpec,
5621
- matchesGuard,
5622
5637
  nameId,
5623
5638
  noopEventStore,
5624
5639
  one,