libpetri 2.13.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +89 -130
  2. package/dist/chunk-6NH64RCU.js +1016 -0
  3. package/dist/chunk-6NH64RCU.js.map +1 -0
  4. package/dist/{chunk-7VJ5CYUU.js → chunk-JZIEWVAV.js} +905 -62
  5. package/dist/chunk-JZIEWVAV.js.map +1 -0
  6. package/dist/{chunk-JVI5HFRX.js → chunk-VCDOKWVU.js} +2 -2
  7. package/dist/{chunk-E3ZWB645.js → chunk-YVIPJ6KM.js} +1 -1
  8. package/dist/chunk-YVIPJ6KM.js.map +1 -0
  9. package/dist/debug/index.d.ts +2 -2
  10. package/dist/debug/index.js +2 -2
  11. package/dist/doclet/index.d.ts +12 -3
  12. package/dist/doclet/index.js +8 -4
  13. package/dist/doclet/index.js.map +1 -1
  14. package/dist/doclet/resources/petrinet-diagrams.css +21 -0
  15. package/dist/doclet/resources/petrinet-diagrams.js +3575 -3573
  16. package/dist/dot-exporter-3STXYK74.js +9 -0
  17. package/dist/{render-ZGZEZ5RK.js → elk-place-YVNQFGXI.js} +3 -258
  18. package/dist/elk-place-YVNQFGXI.js.map +1 -0
  19. package/dist/{event-store-DKTenPbC.d.ts → event-store-BFX_yJ8I.d.ts} +1 -1
  20. package/dist/export/index.d.ts +1 -1
  21. package/dist/export/index.js +2 -2
  22. package/dist/index.d.ts +91 -38
  23. package/dist/index.js +368 -294
  24. package/dist/index.js.map +1 -1
  25. package/dist/pan-zoom-Cp51IkDl.d.ts +33 -0
  26. package/dist/{petri-net-C3LSY-vm.d.ts → petri-net-WSScMyDL.d.ts} +43 -29
  27. package/dist/preprocess-FN3F75JR.js +193 -0
  28. package/dist/preprocess-FN3F75JR.js.map +1 -0
  29. package/dist/render-QOHGDWNE.js +78 -0
  30. package/dist/render-QOHGDWNE.js.map +1 -0
  31. package/dist/render-dom/index.d.ts +28 -29
  32. package/dist/render-dom/index.js +14 -21
  33. package/dist/render-dom/index.js.map +1 -1
  34. package/dist/verification/index.d.ts +273 -7
  35. package/dist/verification/index.js +7 -1
  36. package/dist/verification/index.js.map +1 -1
  37. package/dist/viewer/index.d.ts +24 -32
  38. package/dist/viewer/index.js +9 -1003
  39. package/dist/viewer/index.js.map +1 -1
  40. package/dist/viewer/viewer.css +21 -0
  41. package/dist/viewer/viewer.iife.js +3575 -3573
  42. package/package.json +2 -2
  43. package/dist/chunk-7VJ5CYUU.js.map +0 -1
  44. package/dist/chunk-E3ZWB645.js.map +0 -1
  45. package/dist/dot-exporter-SHBYMMJ3.js +0 -9
  46. package/dist/render-ZGZEZ5RK.js.map +0 -1
  47. /package/dist/{chunk-JVI5HFRX.js.map → chunk-VCDOKWVU.js.map} +0 -0
  48. /package/dist/{dot-exporter-SHBYMMJ3.js.map → dot-exporter-3STXYK74.js.map} +0 -0
@@ -3,6 +3,55 @@ import {
3
3
  latest
4
4
  } from "./chunk-ATT7U5H5.js";
5
5
 
6
+ // src/core/in.ts
7
+ function one(place) {
8
+ return { type: "one", place };
9
+ }
10
+ function exactly(count, place) {
11
+ if (count < 1) {
12
+ throw new Error(`count must be >= 1, got: ${count}`);
13
+ }
14
+ return { type: "exactly", place, count };
15
+ }
16
+ function all(place) {
17
+ return { type: "all", place };
18
+ }
19
+ function atLeast(minimum, place) {
20
+ if (minimum < 1) {
21
+ throw new Error(`minimum must be >= 1, got: ${minimum}`);
22
+ }
23
+ return { type: "at-least", place, minimum };
24
+ }
25
+ function requiredCount(spec) {
26
+ switch (spec.type) {
27
+ case "one":
28
+ return 1;
29
+ case "exactly":
30
+ return spec.count;
31
+ case "all":
32
+ return 1;
33
+ case "at-least":
34
+ return spec.minimum;
35
+ }
36
+ }
37
+ function consumptionCount(spec, available) {
38
+ if (available < requiredCount(spec)) {
39
+ throw new Error(
40
+ `Cannot consume from '${spec.place.name}': available=${available}, required=${requiredCount(spec)}`
41
+ );
42
+ }
43
+ switch (spec.type) {
44
+ case "one":
45
+ return 1;
46
+ case "exactly":
47
+ return spec.count;
48
+ case "all":
49
+ return available;
50
+ case "at-least":
51
+ return available;
52
+ }
53
+ }
54
+
6
55
  // src/core/out.ts
7
56
  function and(...children) {
8
57
  if (children.length === 0) {
@@ -612,6 +661,10 @@ function computePInvariants(matrix, flatNet, initialMarking) {
612
661
  }
613
662
  }
614
663
  if (!isZero) continue;
664
+ if (!rowIsExact(augmented[row], T, P)) {
665
+ invariants.push(rawInvariant(augmented[row], T, P, flatNet, initialMarking));
666
+ continue;
667
+ }
615
668
  const weights = new Array(P);
616
669
  let allNonNegative = true;
617
670
  let hasPositive = false;
@@ -662,6 +715,105 @@ function computePInvariants(matrix, flatNet, initialMarking) {
662
715
  }
663
716
  return invariants;
664
717
  }
718
+ function rowIsExact(row, T, P) {
719
+ for (let i = 0; i < P; i++) {
720
+ if (!Number.isSafeInteger(row[T + i])) return false;
721
+ }
722
+ return true;
723
+ }
724
+ function rawInvariant(row, T, P, flatNet, initialMarking) {
725
+ const weights = new Array(P);
726
+ const support = /* @__PURE__ */ new Set();
727
+ let constant = 0;
728
+ for (let i = 0; i < P; i++) {
729
+ weights[i] = row[T + i];
730
+ if (weights[i] !== 0) {
731
+ support.add(i);
732
+ constant += weights[i] * initialMarking.tokens(flatNet.places[i]);
733
+ }
734
+ }
735
+ return pInvariant(weights, constant, support);
736
+ }
737
+ function validateInvariantsExact(matrix, invariants, flatNet, initialMarking) {
738
+ const nonlinear = nonlinearPlaces(flatNet);
739
+ const valid = [];
740
+ const dropped = [];
741
+ for (const inv of invariants) {
742
+ const reason = exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking);
743
+ if (reason === null) {
744
+ valid.push(inv);
745
+ } else {
746
+ dropped.push({ invariant: inv, reason });
747
+ }
748
+ }
749
+ return { valid, dropped };
750
+ }
751
+ function nonlinearPlaces(flatNet) {
752
+ const nonlinear = /* @__PURE__ */ new Set();
753
+ for (const ft of flatNet.transitions) {
754
+ for (let p = 0; p < ft.consumeAll.length; p++) {
755
+ if (ft.consumeAll[p]) nonlinear.add(p);
756
+ }
757
+ for (const p of ft.resetPlaces) nonlinear.add(p);
758
+ }
759
+ return nonlinear;
760
+ }
761
+ function exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking) {
762
+ const P = matrix.numPlaces();
763
+ const T = matrix.numTransitions();
764
+ if (inv.weights.length !== P) {
765
+ return `weight vector has ${inv.weights.length} entries, expected ${P}`;
766
+ }
767
+ for (let p = 0; p < P; p++) {
768
+ if (!Number.isSafeInteger(inv.weights[p])) {
769
+ return `weight overflow at place '${placeName(flatNet, p)}' (exact value outside this implementation's integer extraction range)`;
770
+ }
771
+ }
772
+ if (!Number.isSafeInteger(inv.constant)) {
773
+ return `constant ${inv.constant} is outside the safe-integer range`;
774
+ }
775
+ for (let p = 0; p < inv.weights.length; p++) {
776
+ if (inv.weights[p] !== 0 && nonlinear.has(p)) {
777
+ return `support intersects consume-all/reset place '${placeName(flatNet, p)}' (non-linear consumption; see Strengthening.lean H1)`;
778
+ }
779
+ }
780
+ const y = inv.weights.map((w) => BigInt(w));
781
+ const incidence = matrix.incidence();
782
+ for (let t = 0; t < T; t++) {
783
+ const row = incidence[t];
784
+ let dot = 0n;
785
+ for (let p = 0; p < P; p++) {
786
+ if (y[p] === 0n) continue;
787
+ if (!Number.isSafeInteger(row[p])) {
788
+ return `incidence entry ${row[p]} at [t=${t}][p=${p}] is outside the safe-integer range`;
789
+ }
790
+ dot += y[p] * BigInt(row[p]);
791
+ }
792
+ if (dot !== 0n) {
793
+ return `y*C is ${dot} (not 0) at ${columnName(flatNet, t)}`;
794
+ }
795
+ }
796
+ let exact = 0n;
797
+ for (let p = 0; p < P; p++) {
798
+ if (y[p] === 0n) continue;
799
+ const tokens = initialMarking.tokens(flatNet.places[p]);
800
+ if (!Number.isSafeInteger(tokens)) {
801
+ return `initial marking of place ${p} (${tokens}) is outside the safe-integer range`;
802
+ }
803
+ exact += y[p] * BigInt(tokens);
804
+ }
805
+ if (exact !== BigInt(inv.constant)) {
806
+ return `constant ${inv.constant} does not match exact y*M0 = ${exact}`;
807
+ }
808
+ return null;
809
+ }
810
+ function placeName(flatNet, p) {
811
+ return flatNet.places[p]?.name ?? `#${p}`;
812
+ }
813
+ function columnName(flatNet, t) {
814
+ const ft = flatNet.transitions[t];
815
+ return ft != null ? `transition '${ft.name}'` : `env-injector column ${t - flatNet.transitions.length}`;
816
+ }
665
817
  function computePSemiflows(matrix, flatNet, initialMarking) {
666
818
  const np = matrix.numPlaces();
667
819
  const nt = matrix.numTransitions();
@@ -953,11 +1105,13 @@ async function createSpacerRunner(timeoutMs) {
953
1105
  const status = await fp.query(errorExpr);
954
1106
  if (status === "unsat") {
955
1107
  let invariantFormula = null;
1108
+ let provenAnswer = null;
956
1109
  const levelInvariants = [];
957
1110
  try {
958
1111
  const answer = fp.getAnswer();
959
1112
  if (answer != null) {
960
1113
  invariantFormula = answer.toString();
1114
+ provenAnswer = answer;
961
1115
  }
962
1116
  } catch {
963
1117
  }
@@ -973,7 +1127,7 @@ async function createSpacerRunner(timeoutMs) {
973
1127
  } catch {
974
1128
  }
975
1129
  }
976
- return { type: "proven", invariantFormula, levelInvariants };
1130
+ return { type: "proven", invariantFormula, levelInvariants, answer: provenAnswer };
977
1131
  }
978
1132
  if (status === "sat") {
979
1133
  let answer = null;
@@ -1058,18 +1212,9 @@ function encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P) {
1058
1212
  const reachBody = reachable.call(...mVars);
1059
1213
  const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
1060
1214
  const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
1061
- let nonNeg = ctx.Bool.val(true);
1062
- for (let i = 0; i < P; i++) {
1063
- nonNeg = ctx.And(nonNeg, mPrimeVars[i].ge(0));
1064
- }
1215
+ const nonNeg = encodeNonNegativity(ctx, mPrimeVars, P);
1065
1216
  const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
1066
- let envBounds = ctx.Bool.val(true);
1067
- for (const [name, bound] of flatNet.environmentBounds) {
1068
- const idx = flatNet.placeIndex.get(name);
1069
- if (idx != null) {
1070
- envBounds = ctx.And(envBounds, mPrimeVars[idx].le(bound));
1071
- }
1072
- }
1217
+ const envBounds = encodeEnvBounds(ctx, flatNet, mPrimeVars);
1073
1218
  const body = ctx.And(reachBody, enabled, fireRelation, nonNeg, invConstraints, envBounds);
1074
1219
  const head = reachable.call(...mPrimeVars);
1075
1220
  const allVars = [...mVars, ...mPrimeVars];
@@ -1086,6 +1231,31 @@ function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
1086
1231
  mPrimeVars.push(Int.const(`mp${i}`));
1087
1232
  }
1088
1233
  const reachBody = reachable.call(...mVars);
1234
+ const fire = encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P);
1235
+ const guard = encodeInjectionGuard(ctx, idx, bound, mVars);
1236
+ const body = ctx.And(reachBody, guard, fire);
1237
+ const head = reachable.call(...mPrimeVars);
1238
+ const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
1239
+ fp.addRule(qRule, `env_inject_${idx}`);
1240
+ }
1241
+ function encodeNonNegativity(ctx, vars, P) {
1242
+ let result = ctx.Bool.val(true);
1243
+ for (let i = 0; i < P; i++) {
1244
+ result = ctx.And(result, vars[i].ge(0));
1245
+ }
1246
+ return result;
1247
+ }
1248
+ function encodeEnvBounds(ctx, flatNet, vars) {
1249
+ let result = ctx.Bool.val(true);
1250
+ for (const [name, bound] of flatNet.environmentBounds) {
1251
+ const idx = flatNet.placeIndex.get(name);
1252
+ if (idx != null) {
1253
+ result = ctx.And(result, vars[idx].le(bound));
1254
+ }
1255
+ }
1256
+ return result;
1257
+ }
1258
+ function encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P) {
1089
1259
  let fire = ctx.Bool.val(true);
1090
1260
  for (let i = 0; i < P; i++) {
1091
1261
  if (i === idx) {
@@ -1094,11 +1264,31 @@ function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
1094
1264
  fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i]));
1095
1265
  }
1096
1266
  }
1097
- const guard = bound === null ? ctx.Bool.val(true) : mVars[idx].lt(bound);
1098
- const body = ctx.And(reachBody, guard, fire);
1099
- const head = reachable.call(...mPrimeVars);
1100
- const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
1101
- fp.addRule(qRule, `env_inject_${idx}`);
1267
+ return fire;
1268
+ }
1269
+ function encodeInjectionGuard(ctx, idx, bound, mVars) {
1270
+ return bound === null ? ctx.Bool.val(true) : mVars[idx].lt(bound);
1271
+ }
1272
+ function encodeStepRelation(ctx, flatNet, mVars, mPrimeVars) {
1273
+ const P = flatNet.places.length;
1274
+ const disjuncts = [];
1275
+ for (const ft of flatNet.transitions) {
1276
+ const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
1277
+ const fire = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
1278
+ const nonNeg = encodeNonNegativity(ctx, mPrimeVars, P);
1279
+ const envBounds = encodeEnvBounds(ctx, flatNet, mPrimeVars);
1280
+ disjuncts.push(ctx.And(enabled, fire, nonNeg, envBounds));
1281
+ }
1282
+ for (const [name, bound] of flatNet.environmentInjection) {
1283
+ const idx = flatNet.placeIndex.get(name);
1284
+ if (idx == null) continue;
1285
+ disjuncts.push(ctx.And(
1286
+ encodeInjectionGuard(ctx, idx, bound, mVars),
1287
+ encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P)
1288
+ ));
1289
+ }
1290
+ if (disjuncts.length === 0) return ctx.Bool.val(false);
1291
+ return ctx.Or(...disjuncts);
1102
1292
  }
1103
1293
  function injectedEnvIndices(flatNet) {
1104
1294
  const out = /* @__PURE__ */ new Map();
@@ -1249,6 +1439,197 @@ function encodeInvariantConstraints(ctx, invariants, mVars, P) {
1249
1439
  return result;
1250
1440
  }
1251
1441
 
1442
+ // src/verification/z3/certificate-checker.ts
1443
+ async function checkCertificate(ctx, answer, flatNet, initialMarking, property, invariants, sinkPlaces, timeoutMs) {
1444
+ try {
1445
+ if (answer == null) {
1446
+ return {
1447
+ type: "unavailable",
1448
+ reason: "invariant missing (Z3 produced no inductive-invariant answer)",
1449
+ invariant: null
1450
+ };
1451
+ }
1452
+ const P = flatNet.places.length;
1453
+ const Int = ctx.Int;
1454
+ const mVars = [];
1455
+ const mPrimeVars = [];
1456
+ for (let i = 0; i < P; i++) {
1457
+ mVars.push(Int.const(`m${i}`));
1458
+ mPrimeVars.push(Int.const(`mp${i}`));
1459
+ }
1460
+ const invariant = extractInvariant(ctx, answer, P, mVars);
1461
+ if (invariant == null) {
1462
+ return {
1463
+ type: "unavailable",
1464
+ reason: "invariant unparseable (no Reachable definition recognized in the Z3 answer)",
1465
+ invariant: String(answer)
1466
+ };
1467
+ }
1468
+ const candidate = invariants.length > 0 ? ctx.And(invariant, encodeInvariantConstraints(ctx, invariants, mVars, P)) : invariant;
1469
+ const failure = await validateCandidate(
1470
+ ctx,
1471
+ candidate,
1472
+ flatNet,
1473
+ initialMarking,
1474
+ property,
1475
+ sinkPlaces,
1476
+ timeoutMs,
1477
+ mVars,
1478
+ mPrimeVars
1479
+ );
1480
+ if (failure == null) {
1481
+ return { type: "passed", invariant: String(candidate) };
1482
+ }
1483
+ return { type: "failed", vc: failure.vc, detail: failure.detail, invariant: String(candidate) };
1484
+ } catch (e) {
1485
+ return {
1486
+ type: "unavailable",
1487
+ reason: `certificate check error: ${e?.message ?? e}`,
1488
+ invariant: null
1489
+ };
1490
+ }
1491
+ }
1492
+ async function validateCandidate(ctx, candidate, flatNet, initialMarking, property, sinkPlaces, timeoutMs, mVars, mPrimeVars) {
1493
+ const P = flatNet.places.length;
1494
+ const Int = ctx.Int;
1495
+ let nonNegM = ctx.Bool.val(true);
1496
+ for (let i = 0; i < P; i++) {
1497
+ nonNegM = ctx.And(nonNegM, mVars[i].ge(0));
1498
+ }
1499
+ const initPairs = mVars.map((v, i) => [
1500
+ v,
1501
+ Int.val(initialMarking.tokens(flatNet.places[i]))
1502
+ ]);
1503
+ const candidateAtInit = ctx.substitute(candidate, ...initPairs);
1504
+ const vc1 = await checkUnsat(ctx, flatNet, mVars, timeoutMs, "initiation (VC1)", [ctx.Not(candidateAtInit)]);
1505
+ if (vc1 != null) return vc1;
1506
+ const primePairs = mVars.map((v, i) => [v, mPrimeVars[i]]);
1507
+ const candidatePrime = ctx.substitute(candidate, ...primePairs);
1508
+ const step = encodeStepRelation(ctx, flatNet, mVars, mPrimeVars);
1509
+ const vc2 = await checkUnsat(
1510
+ ctx,
1511
+ flatNet,
1512
+ mVars,
1513
+ timeoutMs,
1514
+ "consecution (VC2)",
1515
+ [candidate, nonNegM, step, ctx.Not(candidatePrime)]
1516
+ );
1517
+ if (vc2 != null) return vc2;
1518
+ const bad = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
1519
+ const vc3 = await checkUnsat(ctx, flatNet, mVars, timeoutMs, "safety (VC3)", [candidate, nonNegM, bad]);
1520
+ if (vc3 != null) return vc3;
1521
+ return null;
1522
+ }
1523
+ function extractInvariant(ctx, answer, P, mVars) {
1524
+ for (const conjunct of topLevelConjuncts(ctx, answer)) {
1525
+ const invariant = tryExtractDefinition(ctx, conjunct, P, mVars);
1526
+ if (invariant != null) return invariant;
1527
+ }
1528
+ return null;
1529
+ }
1530
+ function topLevelConjuncts(ctx, expr) {
1531
+ if (!ctx.isQuantifier(expr) && ctx.isApp(expr) && String(expr.decl().name()) === "and") {
1532
+ const out = [];
1533
+ const n = expr.numArgs();
1534
+ for (let i = 0; i < n; i++) out.push(expr.arg(i));
1535
+ return out;
1536
+ }
1537
+ return [expr];
1538
+ }
1539
+ function tryExtractDefinition(ctx, expr, P, mVars) {
1540
+ if (ctx.isQuantifier(expr) && expr.is_forall()) {
1541
+ const q = expr;
1542
+ const parts2 = splitDefinition(ctx, q.body(), P);
1543
+ if (parts2 == null) return null;
1544
+ const numVars = q.num_vars();
1545
+ const to = new Array(numVars);
1546
+ for (let j = 0; j < P; j++) {
1547
+ const arg = parts2.app.arg(j);
1548
+ if (!ctx.isVar(arg)) return null;
1549
+ const idx = ctx.getVarIndex(arg);
1550
+ if (idx >= numVars || to[idx] != null) return null;
1551
+ to[idx] = mVars[j];
1552
+ }
1553
+ for (let i = 0; i < numVars; i++) {
1554
+ if (to[i] == null) to[i] = ctx.Int.const(`certFree${i}`);
1555
+ }
1556
+ return ctx.substituteVars(parts2.phi, ...to);
1557
+ }
1558
+ const parts = splitDefinition(ctx, expr, P);
1559
+ if (parts == null) return null;
1560
+ const pairs = [];
1561
+ for (let j = 0; j < P; j++) {
1562
+ const arg = parts.app.arg(j);
1563
+ if (ctx.isVar(arg)) return null;
1564
+ pairs.push([arg, mVars[j]]);
1565
+ }
1566
+ if (pairs.length === 0) return parts.phi;
1567
+ return ctx.substitute(parts.phi, ...pairs);
1568
+ }
1569
+ function splitDefinition(ctx, body, P) {
1570
+ if (ctx.isQuantifier(body) || !ctx.isApp(body)) return null;
1571
+ const b = body;
1572
+ const decl = String(b.decl().name());
1573
+ if ((decl === "=" || decl === "iff") && b.numArgs() === 2) {
1574
+ if (isReachableApp(ctx, b.arg(0), P)) return { app: b.arg(0), phi: b.arg(1) };
1575
+ if (isReachableApp(ctx, b.arg(1), P)) return { app: b.arg(1), phi: b.arg(0) };
1576
+ return null;
1577
+ }
1578
+ if (decl === "=>" && b.numArgs() === 2 && isReachableApp(ctx, b.arg(0), P)) {
1579
+ return { app: b.arg(0), phi: b.arg(1) };
1580
+ }
1581
+ return null;
1582
+ }
1583
+ function isReachableApp(ctx, expr, P) {
1584
+ return ctx.isApp(expr) && String(expr.decl().name()) === "Reachable" && expr.numArgs() === P;
1585
+ }
1586
+ async function checkUnsat(ctx, flatNet, mVars, timeoutMs, vc, assertions) {
1587
+ const solver = new ctx.Solver();
1588
+ if (timeoutMs > 0) {
1589
+ solver.set("timeout", Math.min(timeoutMs, 2147483647));
1590
+ }
1591
+ for (const assertion of assertions) {
1592
+ solver.add(assertion);
1593
+ }
1594
+ const status = await solver.check();
1595
+ if (status === "unsat") return null;
1596
+ if (status === "sat") {
1597
+ const witness = describeWitness(solver, flatNet, mVars);
1598
+ return {
1599
+ vc,
1600
+ detail: `solver returned SATISFIABLE${witness == null ? "" : ` (witness: ${witness})`}`
1601
+ };
1602
+ }
1603
+ let why = null;
1604
+ try {
1605
+ why = String(solver.reasonUnknown());
1606
+ } catch {
1607
+ }
1608
+ return { vc, detail: `solver returned UNKNOWN${why == null || why === "" ? "" : ` (${why})`}` };
1609
+ }
1610
+ function describeWitness(solver, flatNet, mVars) {
1611
+ try {
1612
+ const model = solver.model();
1613
+ const parts = [];
1614
+ let length = 0;
1615
+ for (let i = 0; i < mVars.length; i++) {
1616
+ const value = model.eval(mVars[i], false);
1617
+ const text = value == null ? "" : String(value);
1618
+ if (!/^(-?\d+|\(- \d+\))$/.test(text)) continue;
1619
+ const part = `${flatNet.places[i].name}=${text}`;
1620
+ parts.push(part);
1621
+ length += part.length + 2;
1622
+ if (length > 160) {
1623
+ parts.push("...");
1624
+ break;
1625
+ }
1626
+ }
1627
+ return parts.length === 0 ? null : parts.join(", ");
1628
+ } catch {
1629
+ return null;
1630
+ }
1631
+ }
1632
+
1252
1633
  // src/verification/analysis/dbm.ts
1253
1634
  var EPSILON = 1e-9;
1254
1635
  var DBM = class _DBM {
@@ -1734,18 +2115,8 @@ function inputRequiredCount(spec) {
1734
2115
  return spec.minimum;
1735
2116
  }
1736
2117
  }
1737
- function inputConsumeCount(spec) {
1738
- switch (spec.type) {
1739
- case "one":
1740
- return 1;
1741
- case "exactly":
1742
- return spec.count;
1743
- case "all":
1744
- return 1;
1745
- // Analysis: consume minimum (1 token)
1746
- case "at-least":
1747
- return spec.minimum;
1748
- }
2118
+ function inputConsumeCount(spec, available) {
2119
+ return consumptionCount(spec, available);
1749
2120
  }
1750
2121
  function checkPlaceEnabled(place, required, marking, environmentPlaces, environmentMode) {
1751
2122
  if (!environmentPlaces.has(place)) {
@@ -1763,7 +2134,11 @@ function checkPlaceEnabled(place, required, marking, environmentPlaces, environm
1763
2134
  function fireTransition(marking, transition, outputPlaces, environmentPlaces, environmentMode) {
1764
2135
  const builder = MarkingState.builder().copyFrom(marking);
1765
2136
  for (const spec of transition.inputSpecs) {
1766
- const toConsume = inputConsumeCount(spec);
2137
+ const available = marking.tokens(spec.place);
2138
+ if (available < inputRequiredCount(spec)) {
2139
+ continue;
2140
+ }
2141
+ const toConsume = inputConsumeCount(spec, available);
1767
2142
  consumeFromPlace(builder, spec.place, toConsume, environmentPlaces, environmentMode);
1768
2143
  }
1769
2144
  for (const arc of transition.resets) {
@@ -1788,19 +2163,39 @@ function consumeFromPlace(builder, place, count, environmentPlaces, environmentM
1788
2163
  }
1789
2164
 
1790
2165
  // src/verification/z3/counterexample-decoder.ts
2166
+ function describeDecodeFailure(failure) {
2167
+ if (failure == null) {
2168
+ return "no Reachable applications with concrete arguments found in the derivation";
2169
+ }
2170
+ switch (failure.kind) {
2171
+ case "no-answer":
2172
+ return "Z3 produced no derivation answer";
2173
+ case "traversal-error":
2174
+ return `derivation walk failed: ${failure.message}`;
2175
+ case "non-concrete":
2176
+ return `${failure.skipped} Reachable application(s) had non-concrete arguments`;
2177
+ }
2178
+ }
1791
2179
  function decode(ctx, answer, flatNet) {
1792
2180
  const trace = [];
1793
2181
  const transitions = [];
2182
+ const stateByKey = /* @__PURE__ */ new Map();
1794
2183
  if (answer == null) {
1795
- return { trace, transitions };
2184
+ return { trace, transitions, states: /* @__PURE__ */ new Set(), failure: { kind: "no-answer" } };
1796
2185
  }
2186
+ const counters = { skipped: 0 };
2187
+ let failure = null;
1797
2188
  try {
1798
- extractTrace(ctx, answer, flatNet, trace, transitions);
1799
- } catch {
2189
+ extractTrace(ctx, answer, flatNet, trace, transitions, stateByKey, counters);
2190
+ } catch (e) {
2191
+ failure = { kind: "traversal-error", message: String(e?.message ?? e) };
1800
2192
  }
1801
- return { trace, transitions };
2193
+ if (failure == null && counters.skipped > 0) {
2194
+ failure = { kind: "non-concrete", skipped: counters.skipped };
2195
+ }
2196
+ return { trace, transitions, states: new Set(stateByKey.values()), failure };
1802
2197
  }
1803
- function extractTrace(ctx, expr, flatNet, trace, transitions) {
2198
+ function extractTrace(ctx, expr, flatNet, trace, transitions, stateByKey, counters) {
1804
2199
  if (expr == null) return;
1805
2200
  if (!ctx.isApp(expr)) return;
1806
2201
  let name;
@@ -1817,6 +2212,10 @@ function extractTrace(ctx, expr, flatNet, trace, transitions) {
1817
2212
  const marking = extractMarking(ctx, expr, flatNet);
1818
2213
  if (marking != null) {
1819
2214
  trace.push(marking);
2215
+ const key = marking.toString();
2216
+ if (!stateByKey.has(key)) stateByKey.set(key, marking);
2217
+ } else {
2218
+ counters.skipped++;
1820
2219
  }
1821
2220
  }
1822
2221
  }
@@ -1824,7 +2223,7 @@ function extractTrace(ctx, expr, flatNet, trace, transitions) {
1824
2223
  const numArgs = expr.numArgs();
1825
2224
  for (let i = 0; i < numArgs; i++) {
1826
2225
  const child = expr.arg(i);
1827
- extractTrace(ctx, child, flatNet, trace, transitions);
2226
+ extractTrace(ctx, child, flatNet, trace, transitions, stateByKey, counters);
1828
2227
  }
1829
2228
  } catch {
1830
2229
  }
@@ -1850,6 +2249,238 @@ function extractMarking(ctx, reachableApp, flatNet) {
1850
2249
  return builder.build();
1851
2250
  }
1852
2251
 
2252
+ // src/verification/z3/abstract-replayer.ts
2253
+ function stepName(step) {
2254
+ return step.kind === "fire" ? step.transition : `inject(${step.place})`;
2255
+ }
2256
+ function stateKey(state) {
2257
+ return state.join(",");
2258
+ }
2259
+ function vectorize(marking, flatNet) {
2260
+ return flatNet.places.map((p) => marking.tokens(p));
2261
+ }
2262
+ function toMarkingState(state, flatNet) {
2263
+ const builder = MarkingState.builder();
2264
+ for (let i = 0; i < flatNet.places.length; i++) {
2265
+ if (state[i] > 0) builder.tokens(flatNet.places[i], state[i]);
2266
+ }
2267
+ return builder.build();
2268
+ }
2269
+ function enabledA(state, ft) {
2270
+ const P = state.length;
2271
+ for (let p = 0; p < P; p++) {
2272
+ if (ft.preVector[p] > 0 && state[p] < ft.preVector[p]) return false;
2273
+ }
2274
+ for (const p of ft.readPlaces) {
2275
+ if (state[p] < 1) return false;
2276
+ }
2277
+ for (const p of ft.inhibitorPlaces) {
2278
+ if (state[p] !== 0) return false;
2279
+ }
2280
+ return true;
2281
+ }
2282
+ function fireIndexed(state, ft, resets) {
2283
+ const P = state.length;
2284
+ const next = new Array(P);
2285
+ for (let p = 0; p < P; p++) {
2286
+ if (resets.has(p) || ft.consumeAll[p]) {
2287
+ next[p] = ft.postVector[p];
2288
+ } else {
2289
+ next[p] = state[p] - ft.preVector[p] + ft.postVector[p];
2290
+ }
2291
+ }
2292
+ return next;
2293
+ }
2294
+ function injectA(state, idx) {
2295
+ const next = [...state];
2296
+ next[idx] = next[idx] + 1;
2297
+ return next;
2298
+ }
2299
+ function buildIndex(flatNet) {
2300
+ const resetSets = flatNet.transitions.map((ft) => new Set(ft.resetPlaces));
2301
+ const envInj = /* @__PURE__ */ new Map();
2302
+ for (const [name, bound] of flatNet.environmentInjection) {
2303
+ const idx = flatNet.placeIndex.get(name);
2304
+ if (idx != null) envInj.set(idx, bound);
2305
+ }
2306
+ const envCaps = [];
2307
+ for (const [name, cap] of flatNet.environmentBounds) {
2308
+ const idx = flatNet.placeIndex.get(name);
2309
+ if (idx != null) envCaps.push([idx, cap]);
2310
+ }
2311
+ return { flatNet, resetSets, envInj, envCaps };
2312
+ }
2313
+ function withinEnvBounds(index, state) {
2314
+ for (const [idx, cap] of index.envCaps) {
2315
+ if (state[idx] > cap) return false;
2316
+ }
2317
+ return true;
2318
+ }
2319
+ function successorsIndexed(index, state) {
2320
+ const out = [];
2321
+ const transitions = index.flatNet.transitions;
2322
+ for (let t = 0; t < transitions.length; t++) {
2323
+ const ft = transitions[t];
2324
+ if (!enabledA(state, ft)) continue;
2325
+ const next = fireIndexed(state, ft, index.resetSets[t]);
2326
+ if (!withinEnvBounds(index, next)) continue;
2327
+ out.push({ state: next, step: { kind: "fire", transition: ft.name } });
2328
+ }
2329
+ for (const [name, bound] of index.flatNet.environmentInjection) {
2330
+ const idx = index.flatNet.placeIndex.get(name);
2331
+ if (idx == null) continue;
2332
+ if (bound === null || state[idx] < bound) {
2333
+ out.push({ state: injectA(state, idx), step: { kind: "inject", place: name } });
2334
+ }
2335
+ }
2336
+ return out;
2337
+ }
2338
+ function enabledRelaxEnv(state, ft, envInj) {
2339
+ const P = state.length;
2340
+ for (let p = 0; p < P; p++) {
2341
+ const pre = ft.preVector[p];
2342
+ if (pre <= 0) continue;
2343
+ if (envInj.has(p)) {
2344
+ const bound = envInj.get(p);
2345
+ if (bound !== null && pre > bound) return false;
2346
+ continue;
2347
+ }
2348
+ if (state[p] < pre) return false;
2349
+ }
2350
+ for (const p of ft.readPlaces) {
2351
+ if (envInj.has(p)) {
2352
+ const bound = envInj.get(p);
2353
+ if (bound !== null && bound < 1) return false;
2354
+ continue;
2355
+ }
2356
+ if (state[p] < 1) return false;
2357
+ }
2358
+ for (const p of ft.inhibitorPlaces) {
2359
+ if (state[p] !== 0) return false;
2360
+ }
2361
+ return true;
2362
+ }
2363
+ function isDeadlockA(index, state) {
2364
+ for (const ft of index.flatNet.transitions) {
2365
+ if (enabledRelaxEnv(state, ft, index.envInj)) return false;
2366
+ }
2367
+ return true;
2368
+ }
2369
+ function satisfiesBadIndexed(index, state, property, sinkPlaces) {
2370
+ const flatNet = index.flatNet;
2371
+ switch (property.type) {
2372
+ case "deadlock-free": {
2373
+ if (!isDeadlockA(index, state)) return false;
2374
+ for (const sink of sinkPlaces) {
2375
+ const idx = flatNetIndexOf(flatNet, sink);
2376
+ if (idx >= 0 && state[idx] > 0) return false;
2377
+ }
2378
+ return true;
2379
+ }
2380
+ case "mutual-exclusion": {
2381
+ const idx1 = flatNetIndexOf(flatNet, property.p1);
2382
+ const idx2 = flatNetIndexOf(flatNet, property.p2);
2383
+ if (idx1 < 0 || idx2 < 0) return false;
2384
+ return state[idx1] >= 1 && state[idx2] >= 1;
2385
+ }
2386
+ case "place-bound":
2387
+ case "branch-place-bound": {
2388
+ const idx = flatNetIndexOf(flatNet, property.place);
2389
+ if (idx < 0) return false;
2390
+ return state[idx] > property.bound;
2391
+ }
2392
+ case "joined-or-dead-lettered": {
2393
+ const idx = flatNetIndexOf(flatNet, property.pending);
2394
+ if (idx < 0) return false;
2395
+ return isDeadlockA(index, state) && state[idx] >= 1;
2396
+ }
2397
+ case "unreachable": {
2398
+ let resolved = 0;
2399
+ for (const p of property.places) {
2400
+ const idx = flatNetIndexOf(flatNet, p);
2401
+ if (idx < 0) continue;
2402
+ resolved++;
2403
+ if (state[idx] < 1) return false;
2404
+ }
2405
+ return resolved > 0;
2406
+ }
2407
+ }
2408
+ }
2409
+ function replayCounterexample(flatNet, initial, decodedStates, property, sinkPlaces, options = {}) {
2410
+ const segmentBudget = options.segmentBudget ?? 3;
2411
+ const nodeBudget = options.nodeBudget ?? 1e4;
2412
+ const anchors = /* @__PURE__ */ new Set();
2413
+ for (const s of decodedStates) anchors.add(stateKey(s));
2414
+ if (anchors.size === 0) {
2415
+ return { kind: "exhausted", reason: "no decoded states to replay", nodesExplored: 0 };
2416
+ }
2417
+ const initKey = stateKey(initial);
2418
+ if (!anchors.has(initKey)) {
2419
+ return {
2420
+ kind: "exhausted",
2421
+ reason: "the initial marking is not among the decoded states",
2422
+ nodesExplored: 0
2423
+ };
2424
+ }
2425
+ const index = buildIndex(flatNet);
2426
+ if (satisfiesBadIndexed(index, initial, property, sinkPlaces)) {
2427
+ return { kind: "confirmed", states: [initial], steps: [], nodesExplored: 1 };
2428
+ }
2429
+ const nodes = [{ state: initial, step: null, parent: -1, segment: 0 }];
2430
+ const bestSegment = /* @__PURE__ */ new Map([[initKey, 0]]);
2431
+ const queue = [0];
2432
+ let truncated = false;
2433
+ for (let head = 0; head < queue.length; head++) {
2434
+ const idx = queue[head];
2435
+ const node = nodes[idx];
2436
+ if (node.segment >= segmentBudget) {
2437
+ truncated = true;
2438
+ continue;
2439
+ }
2440
+ for (const succ of successorsIndexed(index, node.state)) {
2441
+ const key = stateKey(succ.state);
2442
+ const segment = anchors.has(key) ? 0 : node.segment + 1;
2443
+ const prior = bestSegment.get(key);
2444
+ if (prior !== void 0 && prior <= segment) continue;
2445
+ bestSegment.set(key, segment);
2446
+ if (nodes.length >= nodeBudget) {
2447
+ return {
2448
+ kind: "exhausted",
2449
+ reason: `search budget exhausted (${nodeBudget} nodes) before reaching a violating state`,
2450
+ nodesExplored: nodes.length
2451
+ };
2452
+ }
2453
+ nodes.push({ state: succ.state, step: succ.step, parent: idx, segment });
2454
+ const childIdx = nodes.length - 1;
2455
+ if (satisfiesBadIndexed(index, succ.state, property, sinkPlaces)) {
2456
+ const chain = reconstruct(nodes, childIdx);
2457
+ return { kind: "confirmed", ...chain, nodesExplored: nodes.length };
2458
+ }
2459
+ queue.push(childIdx);
2460
+ }
2461
+ }
2462
+ if (truncated) {
2463
+ return {
2464
+ kind: "exhausted",
2465
+ reason: `no violating state within ${segmentBudget} abstract step(s) of a decoded state (${bestSegment.size} state(s) explored)`,
2466
+ nodesExplored: nodes.length
2467
+ };
2468
+ }
2469
+ return { kind: "no-chain", nodesExplored: nodes.length };
2470
+ }
2471
+ function reconstruct(nodes, last) {
2472
+ const states = [];
2473
+ const steps = [];
2474
+ for (let i = last; i >= 0; i = nodes[i].parent) {
2475
+ const node = nodes[i];
2476
+ states.push(node.state);
2477
+ if (node.step != null) steps.push(node.step);
2478
+ }
2479
+ states.reverse();
2480
+ steps.reverse();
2481
+ return { states, steps };
2482
+ }
2483
+
1853
2484
  // src/verification/z3/name-coloured-encoder.ts
1854
2485
  function colourSlotBound(coloured, semiflows) {
1855
2486
  const w = (inv, pid) => inv.weights[pid] ?? 0;
@@ -2323,9 +2954,9 @@ function classify(net, mode, carrierPlaces) {
2323
2954
  role: (tn) => roles.get(tn) ?? { type: "ordinary" }
2324
2955
  };
2325
2956
  }
2326
- function fixedRequiredCount(t, placeName) {
2957
+ function fixedRequiredCount(t, placeName2) {
2327
2958
  for (const spec of t.inputSpecs) {
2328
- if (spec.place.name === placeName) {
2959
+ if (spec.place.name === placeName2) {
2329
2960
  switch (spec.type) {
2330
2961
  case "one":
2331
2962
  return 1;
@@ -2388,11 +3019,11 @@ var NameMarking = class _NameMarking {
2388
3019
  return syms ? [...syms.keys()] : [];
2389
3020
  }
2390
3021
  liveSymbols() {
2391
- const all = /* @__PURE__ */ new Set();
3022
+ const all2 = /* @__PURE__ */ new Set();
2392
3023
  for (const syms of this.perPlace.values()) {
2393
- for (const s of syms.keys()) all.add(s);
3024
+ for (const s of syms.keys()) all2.add(s);
2394
3025
  }
2395
- return [...all];
3026
+ return [...all2];
2396
3027
  }
2397
3028
  /**
2398
3029
  * Symmetry-canonical key over `colouredOrder` (the finiteness mechanism). Two
@@ -2559,10 +3190,10 @@ function sharesConsumedInput(h, l, marking) {
2559
3190
  }
2560
3191
  return false;
2561
3192
  }
2562
- function consumedDemand(t, placeName) {
3193
+ function consumedDemand(t, placeName2) {
2563
3194
  let demand = 0;
2564
3195
  for (const spec of t.inputSpecs) {
2565
- if (spec.place.name === placeName) demand += inputRequiredCount2(spec);
3196
+ if (spec.place.name === placeName2) demand += inputRequiredCount2(spec);
2566
3197
  }
2567
3198
  return demand;
2568
3199
  }
@@ -2756,6 +3387,8 @@ var SmtVerifier = class _SmtVerifier {
2756
3387
  _budgetPlaces = /* @__PURE__ */ new Set();
2757
3388
  _environmentMode = alwaysAvailable();
2758
3389
  _timeoutMs = 6e4;
3390
+ _certificateCheck = true;
3391
+ _counterexampleReplay = true;
2759
3392
  _nuMaxClasses = 1e5;
2760
3393
  _fragmentMode = "base";
2761
3394
  _carrierPlaces = /* @__PURE__ */ new Set();
@@ -2809,6 +3442,34 @@ var SmtVerifier = class _SmtVerifier {
2809
3442
  this._timeoutMs = ms;
2810
3443
  return this;
2811
3444
  }
3445
+ /**
3446
+ * Enables/disables the independent IC3 certificate check (default: enabled).
3447
+ *
3448
+ * When a proven verdict comes from the IC3/Spacer path on the flat count
3449
+ * encoding, the synthesized inductive invariant is re-validated with a plain
3450
+ * solver against the UNSTRENGTHENED step relation — VC1 (init), VC2
3451
+ * (consecution), VC3 (safety) — so a Spacer or encoder defect cannot certify
3452
+ * a false PROVEN. A certificate that fails validation downgrades the verdict
3453
+ * to unknown. Structural proofs and the coloured ν-encoding are unaffected.
3454
+ */
3455
+ certificateCheck(enabled) {
3456
+ this._certificateCheck = enabled;
3457
+ return this;
3458
+ }
3459
+ /**
3460
+ * Enables/disables abstract counterexample replay (default: enabled).
3461
+ *
3462
+ * When a violated verdict comes from the flat count encoding, the decoded
3463
+ * counterexample states (an order-free set — the derivation tree is walked in
3464
+ * traversal order, not firing order) are re-executed TS-side against the
3465
+ * abstract semantics the encoder emits (Lean's `fireA`, Basic.lean), searching
3466
+ * for a firing order from M₀ to a property-violating marking. See
3467
+ * `SmtVerificationResult.counterexampleConfirmed` for how each outcome lands.
3468
+ */
3469
+ counterexampleReplay(enabled) {
3470
+ this._counterexampleReplay = enabled;
3471
+ return this;
3472
+ }
2812
3473
  /**
2813
3474
  * Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
2814
3475
  * Route B). When the symbolic name-aware graph would exceed this, the analysis
@@ -2955,6 +3616,7 @@ var SmtVerifier = class _SmtVerifier {
2955
3616
  report.push("=== RESULT ===\n");
2956
3617
  report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
2957
3618
  report.push(" All siphons contain initially marked traps.");
3619
+ report.push(" Certificate check: not applicable (structural proof)");
2958
3620
  return buildResult(
2959
3621
  { type: "proven", method: "structural", inductiveInvariant: null },
2960
3622
  report.join("\n"),
@@ -2968,14 +3630,36 @@ var SmtVerifier = class _SmtVerifier {
2968
3630
  }
2969
3631
  report.push("Phase 3: Computing P-invariants...");
2970
3632
  const matrix = IncidenceMatrix.from(flatNet);
2971
- const invariants = computePInvariants(matrix, flatNet, this._initialMarking);
2972
- const semiflows = computePSemiflows(matrix, flatNet, this._initialMarking);
3633
+ const { valid: invariants, dropped: droppedInvariants } = validateInvariantsExact(
3634
+ matrix,
3635
+ computePInvariants(matrix, flatNet, this._initialMarking),
3636
+ flatNet,
3637
+ this._initialMarking
3638
+ );
3639
+ const { valid: semiflows, dropped: droppedSemiflows } = validateInvariantsExact(
3640
+ matrix,
3641
+ computePSemiflows(matrix, flatNet, this._initialMarking),
3642
+ flatNet,
3643
+ this._initialMarking
3644
+ );
2973
3645
  report.push(` Found: ${invariants.length} P-invariant(s)`);
2974
3646
  const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
2975
3647
  report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
2976
3648
  for (const inv of invariants) {
2977
3649
  report.push(` ${formatInvariant(inv, flatNet)}`);
2978
3650
  }
3651
+ for (const { invariant, reason } of droppedInvariants) {
3652
+ report.push(` Dropped invariant: ${formatInvariant(invariant, flatNet)} - ${reason}`);
3653
+ }
3654
+ if (droppedInvariants.length > 0) {
3655
+ report.push(` Dropped: ${droppedInvariants.length} invariant(s) failed the exact re-check`);
3656
+ }
3657
+ for (const { invariant, reason } of droppedSemiflows) {
3658
+ report.push(` Dropped semiflow: ${formatInvariant(invariant, flatNet)} - ${reason}`);
3659
+ }
3660
+ if (droppedSemiflows.length > 0) {
3661
+ report.push(` Dropped: ${droppedSemiflows.length} semiflow(s) failed the exact re-check`);
3662
+ }
2979
3663
  report.push("");
2980
3664
  report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
2981
3665
  const colouredPlan = hasMatch && nuBounded ? buildColouredPlan(
@@ -3052,7 +3736,45 @@ var SmtVerifier = class _SmtVerifier {
3052
3736
  { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3053
3737
  );
3054
3738
  }
3055
- report.push(" Status: UNSAT (property holds)\n");
3739
+ report.push(" Status: UNSAT (property holds)");
3740
+ if (colouredPlan != null) {
3741
+ report.push(" Certificate check: not applicable (name-coloured encoding)");
3742
+ } else if (!this._certificateCheck) {
3743
+ report.push(" Certificate check: not applicable (disabled)");
3744
+ } else {
3745
+ const certificate = await checkCertificate(
3746
+ runner.ctx,
3747
+ queryResult.answer,
3748
+ flatNet,
3749
+ this._initialMarking,
3750
+ this._property,
3751
+ invariants,
3752
+ this._sinkPlaces,
3753
+ this._timeoutMs
3754
+ );
3755
+ const reason = certificateDowngradeReason(certificate);
3756
+ if (reason != null) {
3757
+ report.push(" Certificate check: FAILED");
3758
+ if (certificate.type !== "passed" && certificate.invariant != null) {
3759
+ report.push(` Uncertified invariant: ${substituteNames(certificate.invariant, flatNet)}`);
3760
+ }
3761
+ report.push("");
3762
+ report.push("=== RESULT ===\n");
3763
+ report.push(`UNKNOWN: ${reason}`);
3764
+ return buildResult(
3765
+ { type: "unknown", reason },
3766
+ report.join("\n"),
3767
+ invariants,
3768
+ [],
3769
+ [],
3770
+ [],
3771
+ performance.now() - start,
3772
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3773
+ );
3774
+ }
3775
+ report.push(" Certificate check: PASSED (init, consecution, safety)");
3776
+ }
3777
+ report.push("");
3056
3778
  const discoveredInvariants = [];
3057
3779
  if (queryResult.invariantFormula != null) {
3058
3780
  discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
@@ -3075,7 +3797,7 @@ var SmtVerifier = class _SmtVerifier {
3075
3797
  report.push("=== RESULT ===\n");
3076
3798
  report.push(`PROVEN (IC3/PDR): ${propDesc}`);
3077
3799
  report.push(" Z3 Spacer proved no reachable state violates the property.");
3078
- report.push(" NOTE: Verification ignores timing constraints and JS guards.");
3800
+ report.push(" NOTE: Verification ignores timing constraints.");
3079
3801
  report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
3080
3802
  return this.applyNuGuard(buildResult(
3081
3803
  {
@@ -3095,29 +3817,92 @@ var SmtVerifier = class _SmtVerifier {
3095
3817
  case "violated": {
3096
3818
  report.push(" Status: SAT (counterexample found)\n");
3097
3819
  const decoded = decode(runner.ctx, queryResult.answer, flatNet);
3820
+ const stats = {
3821
+ places: flatNet.places.length,
3822
+ transitions: flatNet.transitions.length,
3823
+ invariantsFound: invariants.length,
3824
+ structuralResult: structResultStr
3825
+ };
3826
+ let confirmed = null;
3827
+ let trace = decoded.trace;
3828
+ let transitions = decoded.transitions;
3829
+ let replayed = false;
3830
+ if (colouredPlan == null && this._counterexampleReplay) {
3831
+ const assessment = assessCounterexample(
3832
+ flatNet,
3833
+ this._initialMarking,
3834
+ decoded.states,
3835
+ this._property,
3836
+ this._sinkPlaces
3837
+ );
3838
+ if (assessment.kind === "confirmed") {
3839
+ confirmed = true;
3840
+ replayed = true;
3841
+ trace = assessment.trace;
3842
+ transitions = assessment.firings;
3843
+ report.push(" Counterexample replay: CONFIRMED (abstract chain M0 -> bad re-executed)");
3844
+ } else if (assessment.kind === "unconfirmed") {
3845
+ confirmed = false;
3846
+ report.push(` Counterexample replay: UNCONFIRMED (${assessment.note})`);
3847
+ if (decoded.failure != null) {
3848
+ report.push(` Decoder degradation: ${describeDecodeFailure(decoded.failure)}`);
3849
+ }
3850
+ report.push(" The verdict rests on Spacer's SAT answer.");
3851
+ } else {
3852
+ report.push(" Counterexample replay: FAILED");
3853
+ report.push(` Decoded states (order-free set, ${decoded.states.size}):`);
3854
+ for (const m of decoded.states) {
3855
+ report.push(` ${m}`);
3856
+ }
3857
+ if (decoded.trace.length > 0) {
3858
+ report.push(` Raw traversal-order trace (${decoded.trace.length} states):`);
3859
+ for (let i = 0; i < decoded.trace.length; i++) {
3860
+ report.push(` ${i}: ${decoded.trace[i]}`);
3861
+ }
3862
+ }
3863
+ if (decoded.failure != null) {
3864
+ report.push(` Decoder degradation: ${describeDecodeFailure(decoded.failure)}`);
3865
+ }
3866
+ report.push(` Raw Z3 answer: ${truncate(String(queryResult.answer), 2e3)}`);
3867
+ report.push("");
3868
+ report.push("=== RESULT ===\n");
3869
+ report.push(`UNKNOWN: ${assessment.reason}`);
3870
+ return buildResult(
3871
+ { type: "unknown", reason: assessment.reason },
3872
+ report.join("\n"),
3873
+ invariants,
3874
+ [],
3875
+ [],
3876
+ [],
3877
+ performance.now() - start,
3878
+ stats,
3879
+ false
3880
+ );
3881
+ }
3882
+ }
3098
3883
  report.push("=== RESULT ===\n");
3099
3884
  report.push(`VIOLATED: ${propDesc}`);
3100
- if (decoded.trace.length > 0) {
3101
- report.push(` Counterexample trace (${decoded.trace.length} states):`);
3102
- for (let i = 0; i < decoded.trace.length; i++) {
3103
- report.push(` ${i}: ${decoded.trace[i]}`);
3885
+ if (trace.length > 0) {
3886
+ report.push(` Counterexample trace (${replayed ? "replay order, " : ""}${trace.length} states):`);
3887
+ for (let i = 0; i < trace.length; i++) {
3888
+ report.push(` ${i}: ${trace[i]}`);
3104
3889
  }
3105
3890
  }
3106
- if (decoded.transitions.length > 0) {
3107
- report.push(` Firing sequence: ${decoded.transitions.join(" -> ")}`);
3891
+ if (transitions.length > 0) {
3892
+ report.push(` Firing sequence: ${transitions.join(" -> ")}`);
3108
3893
  }
3109
3894
  report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
3110
3895
  report.push(" It may be spurious if timing constraints prevent this sequence.");
3111
- report.push(" JS guards are also ignored in this analysis.");
3112
3896
  return this.applyNuGuard(buildResult(
3113
3897
  { type: "violated" },
3114
3898
  report.join("\n"),
3115
3899
  invariants,
3116
3900
  [],
3117
- decoded.trace,
3118
- decoded.transitions,
3901
+ trace,
3902
+ transitions,
3119
3903
  performance.now() - start,
3120
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3904
+ stats,
3905
+ confirmed
3121
3906
  ), hasMatch, nuBounded, colouredPlan != null);
3122
3907
  }
3123
3908
  case "unknown": {
@@ -3210,6 +3995,51 @@ function isReachabilitySafety(property) {
3210
3995
  return false;
3211
3996
  }
3212
3997
  }
3998
+ function assessCounterexample(flatNet, initialMarking, decodedStates, property, sinkPlaces) {
3999
+ if (decodedStates.size === 0) {
4000
+ return {
4001
+ kind: "unconfirmed",
4002
+ note: "no counterexample states could be decoded from the Spacer answer, so the abstract replay could not run"
4003
+ };
4004
+ }
4005
+ let outcome;
4006
+ try {
4007
+ outcome = replayCounterexample(
4008
+ flatNet,
4009
+ vectorize(initialMarking, flatNet),
4010
+ [...decodedStates].map((m) => vectorize(m, flatNet)),
4011
+ property,
4012
+ sinkPlaces
4013
+ );
4014
+ } catch (e) {
4015
+ outcome = { kind: "exhausted", reason: `replay threw: ${e?.message ?? e}`, nodesExplored: 0 };
4016
+ }
4017
+ switch (outcome.kind) {
4018
+ case "confirmed":
4019
+ return {
4020
+ kind: "confirmed",
4021
+ trace: outcome.states.map((s) => toMarkingState(s, flatNet)),
4022
+ firings: outcome.steps.map(stepName)
4023
+ };
4024
+ case "exhausted":
4025
+ return { kind: "unconfirmed", note: `abstract replay did not complete: ${outcome.reason}` };
4026
+ case "no-chain":
4027
+ return {
4028
+ kind: "downgraded",
4029
+ reason: "counterexample replay found no firing chain to the violation under the abstract semantics, so VIOLATED is withheld"
4030
+ };
4031
+ }
4032
+ }
4033
+ function certificateDowngradeReason(outcome) {
4034
+ switch (outcome.type) {
4035
+ case "passed":
4036
+ return null;
4037
+ case "failed":
4038
+ return `certificate check failed: ${outcome.vc} was not UNSAT - ${outcome.detail}; the IC3 certificate could not be independently re-validated against the unstrengthened step relation, so PROVEN is withheld`;
4039
+ case "unavailable":
4040
+ return `certificate check could not run: ${outcome.reason}; PROVEN is withheld without an independently validated certificate`;
4041
+ }
4042
+ }
3213
4043
  function downgradeToUnknown(result, reason) {
3214
4044
  return {
3215
4045
  ...result,
@@ -3219,9 +4049,13 @@ Downgraded to UNKNOWN: ${reason}
3219
4049
  `,
3220
4050
  discoveredInvariants: [],
3221
4051
  counterexampleTrace: [],
3222
- counterexampleTransitions: []
4052
+ counterexampleTransitions: [],
4053
+ counterexampleConfirmed: null
3223
4054
  };
3224
4055
  }
4056
+ function truncate(s, max) {
4057
+ return s.length <= max ? s : `${s.slice(0, max)}\u2026 (${s.length - max} chars truncated)`;
4058
+ }
3225
4059
  function substituteNames(formula, flatNet) {
3226
4060
  for (let i = flatNet.places.length - 1; i >= 0; i--) {
3227
4061
  formula = formula.replace(new RegExp(`\\bm${i}\\b`, "g"), flatNet.places[i].name);
@@ -3237,10 +4071,10 @@ function formatInvariant(inv, flatNet) {
3237
4071
  parts.push(flatNet.places[idx].name);
3238
4072
  }
3239
4073
  }
3240
- return `${parts.join(" + ")} = ${inv.constant}`;
4074
+ return `${parts.length === 0 ? "0" : parts.join(" + ")} = ${inv.constant}`;
3241
4075
  }
3242
- function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
3243
- return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
4076
+ function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics, counterexampleConfirmed = null) {
4077
+ return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, counterexampleConfirmed, elapsedMs, statistics };
3244
4078
  }
3245
4079
 
3246
4080
  // src/verification/smt-verification-result.ts
@@ -3252,6 +4086,12 @@ function isViolated(result) {
3252
4086
  }
3253
4087
 
3254
4088
  export {
4089
+ one,
4090
+ exactly,
4091
+ all,
4092
+ atLeast,
4093
+ requiredCount,
4094
+ consumptionCount,
3255
4095
  and,
3256
4096
  andPlaces,
3257
4097
  xor,
@@ -3298,13 +4138,16 @@ export {
3298
4138
  flatNetTransitionCount,
3299
4139
  flatNetIndexOf,
3300
4140
  encode,
4141
+ checkCertificate,
3301
4142
  DBM,
3302
4143
  StateClass,
3303
4144
  requireOutputProducingActions,
3304
4145
  StateClassGraph,
4146
+ describeDecodeFailure,
3305
4147
  decode,
4148
+ replayCounterexample,
3306
4149
  SmtVerifier,
3307
4150
  isProven,
3308
4151
  isViolated
3309
4152
  };
3310
- //# sourceMappingURL=chunk-7VJ5CYUU.js.map
4153
+ //# sourceMappingURL=chunk-JZIEWVAV.js.map