libpetri 3.0.0 → 4.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.
Files changed (38) hide show
  1. package/README.md +54 -2
  2. package/dist/chunk-KO6TSB47.js +1016 -0
  3. package/dist/chunk-KO6TSB47.js.map +1 -0
  4. package/dist/{chunk-5W6SVYPD.js → chunk-WYCGGQAW.js} +1744 -737
  5. package/dist/chunk-WYCGGQAW.js.map +1 -0
  6. package/dist/debug/index.d.ts +2 -2
  7. package/dist/doclet/index.d.ts +12 -3
  8. package/dist/doclet/index.js +5 -1
  9. package/dist/doclet/index.js.map +1 -1
  10. package/dist/doclet/resources/petrinet-diagrams.css +21 -0
  11. package/dist/doclet/resources/petrinet-diagrams.js +6366 -6360
  12. package/dist/{render-ZGZEZ5RK.js → elk-place-YVNQFGXI.js} +3 -258
  13. package/dist/elk-place-YVNQFGXI.js.map +1 -0
  14. package/dist/{event-store-Df_sAVQ_.d.ts → event-store-2FOAeUyh.d.ts} +1 -1
  15. package/dist/export/index.d.ts +1 -1
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +6 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/pan-zoom-Cp51IkDl.d.ts +33 -0
  20. package/dist/{petri-net-UQBBkvLl.d.ts → petri-net-hduM6tJf.d.ts} +20 -1
  21. package/dist/preprocess-FN3F75JR.js +193 -0
  22. package/dist/preprocess-FN3F75JR.js.map +1 -0
  23. package/dist/render-QOHGDWNE.js +78 -0
  24. package/dist/render-QOHGDWNE.js.map +1 -0
  25. package/dist/render-dom/index.d.ts +28 -29
  26. package/dist/render-dom/index.js +14 -21
  27. package/dist/render-dom/index.js.map +1 -1
  28. package/dist/verification/index.d.ts +440 -71
  29. package/dist/verification/index.js +43 -5
  30. package/dist/verification/index.js.map +1 -1
  31. package/dist/viewer/index.d.ts +24 -32
  32. package/dist/viewer/index.js +9 -1003
  33. package/dist/viewer/index.js.map +1 -1
  34. package/dist/viewer/viewer.css +21 -0
  35. package/dist/viewer/viewer.iife.js +6366 -6360
  36. package/package.json +5 -8
  37. package/dist/chunk-5W6SVYPD.js.map +0 -1
  38. package/dist/render-ZGZEZ5RK.js.map +0 -1
@@ -409,7 +409,7 @@ function flatten(net, environmentPlaces = /* @__PURE__ */ new Set(), environment
409
409
  for (const arc of t.reads) allPlacesSet.set(arc.place.name, arc.place);
410
410
  for (const arc of t.resets) allPlacesSet.set(arc.place.name, arc.place);
411
411
  }
412
- const places = [...allPlacesSet.values()].sort((a, b) => a.name.localeCompare(b.name));
412
+ const places = [...allPlacesSet.values()].sort((a, b) => compareCodePoints(a.name, b.name));
413
413
  const placeIndex = /* @__PURE__ */ new Map();
414
414
  for (let i = 0; i < places.length; i++) {
415
415
  placeIndex.set(places[i].name, i);
@@ -497,6 +497,20 @@ function enumerateOutputBranches(t) {
497
497
  }
498
498
  return [/* @__PURE__ */ new Set()];
499
499
  }
500
+ function compareCodePoints(a, b) {
501
+ const ia = a[Symbol.iterator]();
502
+ const ib = b[Symbol.iterator]();
503
+ for (; ; ) {
504
+ const na = ia.next();
505
+ const nb = ib.next();
506
+ if (na.done && nb.done) return 0;
507
+ if (na.done) return -1;
508
+ if (nb.done) return 1;
509
+ const ca = na.value.codePointAt(0);
510
+ const cb = nb.value.codePointAt(0);
511
+ if (ca !== cb) return ca - cb;
512
+ }
513
+ }
500
514
 
501
515
  // src/verification/encoding/incidence-matrix.ts
502
516
  var IncidenceMatrix = class _IncidenceMatrix {
@@ -661,42 +675,21 @@ function computePInvariants(matrix, flatNet, initialMarking) {
661
675
  }
662
676
  }
663
677
  if (!isZero) continue;
678
+ if (!rowIsExact(augmented[row], T, P)) {
679
+ invariants.push(rawInvariant(augmented[row], T, P, flatNet, initialMarking));
680
+ continue;
681
+ }
664
682
  const weights = new Array(P);
665
- let allNonNegative = true;
666
683
  let hasPositive = false;
684
+ let hasNegative = false;
667
685
  for (let i = 0; i < P; i++) {
668
686
  weights[i] = augmented[row][T + i];
669
- if (weights[i] < 0) {
670
- allNonNegative = false;
671
- break;
672
- }
673
687
  if (weights[i] > 0) hasPositive = true;
688
+ if (weights[i] < 0) hasNegative = true;
674
689
  }
675
- if (!allNonNegative) {
676
- let allNonPositive = true;
677
- for (let i = 0; i < P; i++) {
678
- if (augmented[row][T + i] > 0) {
679
- allNonPositive = false;
680
- break;
681
- }
682
- }
683
- if (allNonPositive) {
684
- for (let i = 0; i < P; i++) {
685
- weights[i] = -augmented[row][T + i];
686
- }
687
- hasPositive = true;
688
- allNonNegative = true;
689
- }
690
- }
691
- if (!allNonNegative || !hasPositive) continue;
692
- let g = 0;
693
- for (const w of weights) {
694
- if (w > 0) g = gcd(g, w);
695
- }
696
- if (g > 1) {
697
- for (let i = 0; i < P; i++) {
698
- weights[i] = weights[i] / g;
699
- }
690
+ if (!hasPositive && !hasNegative) continue;
691
+ if (!hasPositive) {
692
+ for (let i = 0; i < P; i++) weights[i] = -weights[i];
700
693
  }
701
694
  const support = /* @__PURE__ */ new Set();
702
695
  let constant = 0;
@@ -711,6 +704,123 @@ function computePInvariants(matrix, flatNet, initialMarking) {
711
704
  }
712
705
  return invariants;
713
706
  }
707
+ function rowIsExact(row, T, P) {
708
+ for (let i = 0; i < P; i++) {
709
+ if (!Number.isSafeInteger(row[T + i])) return false;
710
+ }
711
+ return true;
712
+ }
713
+ function rawInvariant(row, T, P, flatNet, initialMarking) {
714
+ const weights = new Array(P);
715
+ const support = /* @__PURE__ */ new Set();
716
+ let constant = 0;
717
+ for (let i = 0; i < P; i++) {
718
+ weights[i] = row[T + i];
719
+ if (weights[i] !== 0) {
720
+ support.add(i);
721
+ constant += weights[i] * initialMarking.tokens(flatNet.places[i]);
722
+ }
723
+ }
724
+ return pInvariant(weights, constant, support);
725
+ }
726
+ function validateInvariantsExact(matrix, invariants, flatNet, initialMarking) {
727
+ const nonlinear = nonlinearPlaces(flatNet);
728
+ const valid = [];
729
+ const dropped = [];
730
+ for (const inv of invariants) {
731
+ const reason = exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking);
732
+ if (reason === null) {
733
+ valid.push(inv);
734
+ } else {
735
+ dropped.push({ invariant: inv, reason });
736
+ }
737
+ }
738
+ return { valid, dropped };
739
+ }
740
+ function nonlinearPlaces(flatNet) {
741
+ const nonlinear = /* @__PURE__ */ new Set();
742
+ for (const ft of flatNet.transitions) {
743
+ for (let p = 0; p < ft.consumeAll.length; p++) {
744
+ if (ft.consumeAll[p]) nonlinear.add(p);
745
+ }
746
+ for (const p of ft.resetPlaces) nonlinear.add(p);
747
+ }
748
+ return nonlinear;
749
+ }
750
+ function exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking) {
751
+ const P = matrix.numPlaces();
752
+ const T = matrix.numTransitions();
753
+ if (inv.weights.length !== P) {
754
+ return `weight vector has ${inv.weights.length} entries, expected ${P}`;
755
+ }
756
+ for (let p = 0; p < P; p++) {
757
+ if (!Number.isSafeInteger(inv.weights[p])) {
758
+ return `weight overflow at place '${placeName(flatNet, p)}' (exact value outside this implementation's integer extraction range)`;
759
+ }
760
+ }
761
+ if (!Number.isSafeInteger(inv.constant)) {
762
+ return `constant ${inv.constant} is outside the safe-integer range`;
763
+ }
764
+ for (let p = 0; p < inv.weights.length; p++) {
765
+ if (inv.weights[p] !== 0 && nonlinear.has(p)) {
766
+ return `support intersects consume-all/reset place '${placeName(flatNet, p)}' (non-linear consumption; see Strengthening.lean H1)`;
767
+ }
768
+ }
769
+ const y = inv.weights.map((w) => BigInt(w));
770
+ const incidence = matrix.incidence();
771
+ for (let t = 0; t < T; t++) {
772
+ const row = incidence[t];
773
+ let dot = 0n;
774
+ for (let p = 0; p < P; p++) {
775
+ if (y[p] === 0n) continue;
776
+ if (!Number.isSafeInteger(row[p])) {
777
+ return `incidence entry ${row[p]} at [t=${t}][p=${p}] is outside the safe-integer range`;
778
+ }
779
+ dot += y[p] * BigInt(row[p]);
780
+ }
781
+ if (dot !== 0n) {
782
+ return `y*C is ${dot} (not 0) at ${columnName(flatNet, t)}`;
783
+ }
784
+ }
785
+ let exact = 0n;
786
+ for (let p = 0; p < P; p++) {
787
+ if (y[p] === 0n) continue;
788
+ const tokens = initialMarking.tokens(flatNet.places[p]);
789
+ if (!Number.isSafeInteger(tokens)) {
790
+ return `initial marking of place ${p} (${tokens}) is outside the safe-integer range`;
791
+ }
792
+ exact += y[p] * BigInt(tokens);
793
+ }
794
+ if (exact !== BigInt(inv.constant)) {
795
+ return `constant ${inv.constant} does not match exact y*M0 = ${exact}`;
796
+ }
797
+ return null;
798
+ }
799
+ function placeName(flatNet, p) {
800
+ return flatNet.places[p]?.name ?? `#${p}`;
801
+ }
802
+ function columnName(flatNet, t) {
803
+ const ft = flatNet.transitions[t];
804
+ return ft != null ? `transition '${ft.name}'` : `env-injector column ${t - flatNet.transitions.length}`;
805
+ }
806
+ function sameInvariant(a, b) {
807
+ if (a.constant !== b.constant || a.weights.length !== b.weights.length) return false;
808
+ for (let i = 0; i < a.weights.length; i++) {
809
+ if (a.weights[i] !== b.weights[i]) return false;
810
+ }
811
+ return true;
812
+ }
813
+ function strengthenWithSemiflows(invariants, semiflows) {
814
+ const strengthened = [...invariants];
815
+ let added = 0;
816
+ for (const sf of semiflows) {
817
+ if (!strengthened.some((inv) => sameInvariant(inv, sf))) {
818
+ strengthened.push(sf);
819
+ added++;
820
+ }
821
+ }
822
+ return { invariants: strengthened, added };
823
+ }
714
824
  function computePSemiflows(matrix, flatNet, initialMarking) {
715
825
  const np = matrix.numPlaces();
716
826
  const nt = matrix.numTransitions();
@@ -798,6 +908,7 @@ function keepSupportMinimal(rows) {
798
908
  function isCoveredByInvariants(invariants, numPlaces) {
799
909
  const covered = new Array(numPlaces).fill(false);
800
910
  for (const inv of invariants) {
911
+ if (inv.weights.some((w) => w < 0)) continue;
801
912
  for (const idx of inv.support) {
802
913
  if (idx < numPlaces) covered[idx] = true;
803
914
  }
@@ -825,6 +936,19 @@ function gcd(a, b) {
825
936
  }
826
937
  return a;
827
938
  }
939
+ function canonicalInvariantOrder(invariants) {
940
+ const lex = (a, b) => {
941
+ const n = Math.min(a.length, b.length);
942
+ for (let i = 0; i < n; i++) {
943
+ if (a[i] !== b[i]) return a[i] - b[i];
944
+ }
945
+ return a.length - b.length;
946
+ };
947
+ const support = (inv) => [...inv.support].sort((x, y) => x - y);
948
+ return [...invariants].sort(
949
+ (a, b) => lex(support(a), support(b)) || lex(a.weights, b.weights) || a.constant - b.constant
950
+ );
951
+ }
828
952
 
829
953
  // src/verification/invariant/structural-check.ts
830
954
  var MAX_PLACES_FOR_SIPHON_ANALYSIS = 50;
@@ -987,315 +1111,675 @@ function isSubsetOf(sub, sup) {
987
1111
  return true;
988
1112
  }
989
1113
 
990
- // src/verification/z3/spacer-runner.ts
991
- import { init } from "z3-solver";
992
- async function createSpacerRunner(timeoutMs) {
993
- const { Context } = await init();
994
- const ctx = new Context("main");
995
- const fp = new ctx.Fixedpoint();
996
- fp.set("engine", "spacer");
997
- if (timeoutMs > 0) {
998
- fp.set("timeout", Math.min(timeoutMs, 2147483647));
999
- }
1000
- async function query(errorExpr, reachableDecl) {
1001
- try {
1002
- const status = await fp.query(errorExpr);
1003
- if (status === "unsat") {
1004
- let invariantFormula = null;
1005
- const levelInvariants = [];
1006
- try {
1007
- const answer = fp.getAnswer();
1008
- if (answer != null) {
1009
- invariantFormula = answer.toString();
1010
- }
1011
- } catch {
1012
- }
1013
- if (reachableDecl != null) {
1014
- try {
1015
- const levels = fp.getNumLevels(reachableDecl);
1016
- for (let i = 0; i < levels; i++) {
1017
- const cover = fp.getCoverDelta(i, reachableDecl);
1018
- if (cover != null && !ctx.isTrue(cover)) {
1019
- levelInvariants.push(`Level ${i}: ${cover.toString()}`);
1020
- }
1021
- }
1022
- } catch {
1023
- }
1024
- }
1025
- return { type: "proven", invariantFormula, levelInvariants };
1026
- }
1027
- if (status === "sat") {
1028
- let answer = null;
1029
- try {
1030
- answer = fp.getAnswer();
1031
- } catch {
1032
- }
1033
- return { type: "violated", answer };
1034
- }
1035
- return { type: "unknown", reason: fp.getReasonUnknown() };
1036
- } catch (e) {
1037
- return { type: "unknown", reason: `Z3 exception: ${e.message ?? e}` };
1038
- }
1114
+ // src/verification/z3/z3-process.ts
1115
+ import { spawn, spawnSync } from "child_process";
1116
+ import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
1117
+ import * as path from "path";
1118
+
1119
+ // src/verification/z3/smt-text.ts
1120
+ function classifyFirstLine(stdout) {
1121
+ for (const raw of stdout.split("\n")) {
1122
+ const line = raw.trim();
1123
+ if (line === "sat" || line === "unsat" || line === "unknown") return line;
1124
+ }
1125
+ return null;
1126
+ }
1127
+ function timeoutLine(stdout) {
1128
+ return stdout.split("\n").some((l) => l.trim() === "timeout");
1129
+ }
1130
+ function errorLine(text) {
1131
+ for (const raw of text.split("\n")) {
1132
+ const line = raw.trim();
1133
+ if (line.startsWith("(error")) return line;
1134
+ }
1135
+ return null;
1136
+ }
1137
+ function sexprEnd(s, start) {
1138
+ let depth = 0;
1139
+ let inString = false;
1140
+ let inSymbol = false;
1141
+ for (let i = start; i < s.length; i++) {
1142
+ const c = s[i];
1143
+ if (inString) {
1144
+ if (c === '"') inString = false;
1145
+ } else if (inSymbol) {
1146
+ if (c === "|") inSymbol = false;
1147
+ } else if (c === '"') {
1148
+ inString = true;
1149
+ } else if (c === "|") {
1150
+ inSymbol = true;
1151
+ } else if (c === "(") {
1152
+ depth++;
1153
+ } else if (c === ")") {
1154
+ depth--;
1155
+ if (depth === 0) return i + 1;
1156
+ }
1157
+ }
1158
+ return -1;
1159
+ }
1160
+ function extractDefineFuns(output) {
1161
+ const defs = [];
1162
+ let from = 0;
1163
+ for (; ; ) {
1164
+ const pos = output.indexOf("(define-fun", from);
1165
+ if (pos < 0) break;
1166
+ const end = sexprEnd(output, pos);
1167
+ if (end < 0) break;
1168
+ defs.push(output.slice(pos, end));
1169
+ from = end;
1170
+ }
1171
+ return defs;
1172
+ }
1173
+ function extractInvariant(output) {
1174
+ const defs = extractDefineFuns(output);
1175
+ return defs.length === 0 ? null : defs.join("\n");
1176
+ }
1177
+
1178
+ // src/verification/z3/z3-process.ts
1179
+ var Z3_ENV = "LIBPETRI_Z3";
1180
+ var DUMP_ENV = "LIBPETRI_SMT_DUMP";
1181
+ var GRACE_MS = 1e3;
1182
+ var VERSION_PROBE_MS = 5e3;
1183
+ var MIN_Z3_VERSION = { major: 4, minor: 8, patch: 0 };
1184
+ function parseZ3Version(text) {
1185
+ const m = /Z3 version (\d+)\.(\d+)(?:\.(\d+))?/.exec(text);
1186
+ if (m == null) return null;
1187
+ return { major: Number(m[1]), minor: Number(m[2]), patch: m[3] == null ? 0 : Number(m[3]) };
1188
+ }
1189
+ function formatZ3Version(v) {
1190
+ return `${v.major}.${v.minor}.${v.patch}`;
1191
+ }
1192
+ function compareZ3Version(a, b) {
1193
+ return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
1194
+ }
1195
+ var Z3Unavailable = class extends Error {
1196
+ constructor(message) {
1197
+ super(message);
1198
+ this.name = "Z3Unavailable";
1039
1199
  }
1040
- function dispose() {
1200
+ };
1201
+ var Z3ProcessError = class extends Error {
1202
+ constructor(message) {
1203
+ super(message);
1204
+ this.name = "Z3ProcessError";
1205
+ }
1206
+ };
1207
+ function replySucceeded(reply) {
1208
+ return reply.exit.kind === "exited" && reply.exit.code === 0;
1209
+ }
1210
+ function argsFor(timeoutMs) {
1211
+ return ["-smt2", "-in", `-t:${timeoutMs}`, `-T:${hardTimeoutSecs(timeoutMs)}`];
1212
+ }
1213
+ function hardTimeoutSecs(timeoutMs) {
1214
+ return Math.max(1, Math.ceil((timeoutMs + GRACE_MS) / 1e3));
1215
+ }
1216
+ function watchdogMs(timeoutMs) {
1217
+ return timeoutMs + 2 * GRACE_MS;
1218
+ }
1219
+ function timeoutBudget(timeoutMs) {
1220
+ return Math.max(1, Math.floor(Number.isFinite(timeoutMs) ? timeoutMs : 1));
1221
+ }
1222
+ function failureReason(reply, timeoutMs) {
1223
+ if (timeoutLine(reply.stdout)) {
1224
+ return `z3 hard timeout after ${hardTimeoutSecs(timeoutMs)}s`;
1225
+ }
1226
+ if (reply.exit.kind === "killed") {
1227
+ return `z3 did not exit within ${watchdogMs(timeoutMs)} ms and was killed`;
1228
+ }
1229
+ const err = errorLine(reply.stdout) ?? errorLine(reply.stderr);
1230
+ if (err != null) return `Z3 error: ${err}`;
1231
+ const stderr = reply.stderr.trim();
1232
+ if (stderr !== "") return `Z3 error: ${stderr}`;
1233
+ return `Unexpected Z3 output: ${reply.stdout.trim()}`;
1234
+ }
1235
+ function locateZ3(program, env = process.env) {
1236
+ const isFile = (p) => {
1041
1237
  try {
1042
- fp.release();
1238
+ return existsSync(p) && statSync(p).isFile();
1043
1239
  } catch {
1240
+ return false;
1044
1241
  }
1045
- }
1046
- return {
1047
- ctx,
1048
- fp,
1049
- query,
1050
- dispose
1051
1242
  };
1243
+ if (program.includes("/") || program.includes(path.sep) || path.isAbsolute(program)) {
1244
+ return isFile(program) ? program : null;
1245
+ }
1246
+ const searchPath = env["PATH"] ?? "";
1247
+ const windows = process.platform === "win32";
1248
+ for (const dir of searchPath.split(path.delimiter)) {
1249
+ if (dir === "") continue;
1250
+ const candidate2 = path.join(dir, program);
1251
+ if (isFile(candidate2)) return candidate2;
1252
+ if (windows && isFile(candidate2 + ".exe")) return candidate2 + ".exe";
1253
+ }
1254
+ return null;
1052
1255
  }
1053
-
1054
- // src/verification/encoding/flat-net.ts
1055
- function flatNetPlaceCount(net) {
1056
- return net.places.length;
1256
+ function z3SolverAt(program, env = process.env) {
1257
+ const located = locateZ3(program, env);
1258
+ if (located == null) {
1259
+ throw new Z3Unavailable(
1260
+ `z3 binary not found: ${program}; install z3 >= ${formatZ3Version(MIN_Z3_VERSION)} or set ${Z3_ENV}`
1261
+ );
1262
+ }
1263
+ const probe = spawnSync(located, ["--version"], {
1264
+ encoding: "utf8",
1265
+ timeout: VERSION_PROBE_MS,
1266
+ stdio: ["ignore", "pipe", "pipe"]
1267
+ });
1268
+ if (probe.error != null) {
1269
+ if (probe.error.code === "ETIMEDOUT") {
1270
+ throw new Z3Unavailable(`${program} --version did not answer within ${VERSION_PROBE_MS} ms`);
1271
+ }
1272
+ throw new Z3Unavailable(`failed to spawn ${program}: ${probe.error.message}`);
1273
+ }
1274
+ const version = parseZ3Version(probe.stdout ?? "");
1275
+ if (version == null) {
1276
+ const line = `${probe.stdout ?? ""}
1277
+ ${probe.stderr ?? ""}`.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? "";
1278
+ throw new Z3Unavailable(`z3 --version did not report a version: ${line}`);
1279
+ }
1280
+ if (compareZ3Version(version, MIN_Z3_VERSION) < 0) {
1281
+ throw new Z3Unavailable(
1282
+ `z3 ${formatZ3Version(version)} is older than the minimum ${formatZ3Version(MIN_Z3_VERSION)}`
1283
+ );
1284
+ }
1285
+ return { program: located, version, dumpDir: null };
1057
1286
  }
1058
- function flatNetTransitionCount(net) {
1059
- return net.transitions.length;
1287
+ function resolveZ3(env = process.env) {
1288
+ const configured = env[Z3_ENV];
1289
+ const program = configured == null || configured.trim() === "" ? "z3" : configured;
1290
+ const dump = env[DUMP_ENV];
1291
+ const solver = z3SolverAt(program, env);
1292
+ return { ...solver, dumpDir: dump == null || dump.trim() === "" ? null : dump };
1060
1293
  }
1061
- function flatNetIndexOf(net, place) {
1062
- return net.placeIndex.get(place.name) ?? -1;
1294
+ function z3Available(env = process.env) {
1295
+ try {
1296
+ resolveZ3(env);
1297
+ return true;
1298
+ } catch {
1299
+ return false;
1300
+ }
1301
+ }
1302
+ var dumpCounter = 0;
1303
+ function dumpSlot(solver, phase, script2) {
1304
+ if (solver.dumpDir == null) return null;
1305
+ dumpCounter += 1;
1306
+ try {
1307
+ mkdirSync(solver.dumpDir, { recursive: true });
1308
+ const base = path.join(solver.dumpDir, `${String(dumpCounter).padStart(3, "0")}-${phase}`);
1309
+ writeFileSync(`${base}.smt2`, script2);
1310
+ return base;
1311
+ } catch {
1312
+ return null;
1313
+ }
1314
+ }
1315
+ function dumpWrite(file, text) {
1316
+ try {
1317
+ writeFileSync(file, text);
1318
+ } catch {
1319
+ }
1320
+ }
1321
+ function runZ3Text(solver, script2, phase, timeoutMs, extraArgs = []) {
1322
+ const budget = timeoutBudget(timeoutMs);
1323
+ const base = dumpSlot(solver, phase, script2);
1324
+ return new Promise((resolve, reject) => {
1325
+ const child = spawn(solver.program, [...argsFor(budget), ...extraArgs], {
1326
+ stdio: ["pipe", "pipe", "pipe"]
1327
+ });
1328
+ const out = [];
1329
+ const err = [];
1330
+ let killed = false;
1331
+ let settled = false;
1332
+ child.stdout.on("data", (chunk) => out.push(chunk));
1333
+ child.stderr.on("data", (chunk) => err.push(chunk));
1334
+ child.stdin.on("error", () => {
1335
+ });
1336
+ const watchdog = setTimeout(() => {
1337
+ killed = true;
1338
+ child.kill("SIGKILL");
1339
+ }, watchdogMs(budget));
1340
+ child.on("error", (e) => {
1341
+ if (settled) return;
1342
+ settled = true;
1343
+ clearTimeout(watchdog);
1344
+ reject(new Z3ProcessError(`failed to spawn ${solver.program}: ${e.message}`));
1345
+ });
1346
+ child.on("close", (code) => {
1347
+ if (settled) return;
1348
+ settled = true;
1349
+ clearTimeout(watchdog);
1350
+ const reply = {
1351
+ stdout: Buffer.concat(out).toString("utf8"),
1352
+ stderr: Buffer.concat(err).toString("utf8"),
1353
+ exit: killed ? { kind: "killed" } : { kind: "exited", code }
1354
+ };
1355
+ if (base != null) {
1356
+ dumpWrite(`${base}.out`, reply.stdout);
1357
+ if (reply.stderr.trim() !== "") dumpWrite(`${base}.err`, reply.stderr);
1358
+ }
1359
+ resolve(reply);
1360
+ });
1361
+ child.stdin.end(script2);
1362
+ });
1363
+ }
1364
+
1365
+ // src/verification/z3/spacer-runner.ts
1366
+ async function runZ3Spacer(solver, timeoutMs, smt2, phase) {
1367
+ let reply;
1368
+ try {
1369
+ reply = await runZ3Text(solver, smt2, phase, timeoutMs, ["fp.engine=spacer"]);
1370
+ } catch (e) {
1371
+ return { type: "unknown", reason: String(e?.message ?? e) };
1372
+ }
1373
+ const stdout = reply.stdout.trim();
1374
+ switch (classifyFirstLine(stdout)) {
1375
+ // unsat => no inductive invariant excludes the bad state => VIOLATED.
1376
+ case "unsat":
1377
+ return { type: "violated", answer: stdout };
1378
+ // sat => an inductive invariant exists => PROVEN.
1379
+ case "sat":
1380
+ return { type: "proven", invariantFormula: extractInvariant(stdout) };
1381
+ case "unknown":
1382
+ return { type: "unknown", reason: "Z3 answered unknown" };
1383
+ default:
1384
+ return { type: "unknown", reason: failureReason(reply, timeoutBudget(timeoutMs)) };
1385
+ }
1063
1386
  }
1064
1387
 
1065
1388
  // src/verification/z3/smt-encoder.ts
1066
- function encode(ctx, fp, flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set()) {
1389
+ function encode(flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set(), produceProofs = false) {
1067
1390
  const P = flatNet.places.length;
1068
- const Int = ctx.Int;
1069
- const Bool_ = ctx.Bool;
1070
- const intSort = Int.sort();
1071
- const boolSort = Bool_.sort();
1072
- const markingSorts = new Array(P).fill(intSort);
1073
- const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
1074
- fp.registerRelation(reachable);
1075
- const error = ctx.Function.declare("Error", boolSort);
1076
- fp.registerRelation(error);
1077
- const m0Args = [];
1078
- for (let i = 0; i < P; i++) {
1079
- const tokens = initialMarking.tokens(flatNet.places[i]);
1080
- m0Args.push(Int.val(tokens));
1081
- }
1082
- const initFact = reachable.call(...m0Args);
1083
- fp.addRule(initFact, "init");
1084
- for (let t = 0; t < flatNet.transitions.length; t++) {
1085
- const ft = flatNet.transitions[t];
1086
- encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P);
1087
- }
1391
+ const lines = [];
1392
+ const envInject = resolveEnvInjection(flatNet);
1393
+ if (produceProofs) lines.push("(set-option :produce-proofs true)");
1394
+ lines.push("(set-logic HORN)");
1395
+ lines.push("");
1396
+ lines.push(`(declare-fun Reachable (${ints(P).join(" ")}) Bool)`);
1397
+ lines.push("(declare-fun Error () Bool)");
1398
+ lines.push("");
1399
+ const mVars = vars(P, "");
1400
+ const mpVars = vars(P, "p");
1401
+ const m0 = [];
1402
+ for (let i = 0; i < P; i++) m0.push(String(initialMarking.tokens(flatNet.places[i])));
1403
+ lines.push(`(assert (Reachable ${m0.join(" ")}))`);
1404
+ lines.push("");
1405
+ for (const ft of flatNet.transitions) {
1406
+ lines.push(encodeTransitionRule(flatNet, ft, mVars, mpVars, invariants));
1407
+ }
1408
+ for (const inj of envInject) {
1409
+ lines.push(encodeInjectionRule(P, inj.pid, inj.bound, mVars, mpVars));
1410
+ }
1411
+ lines.push("");
1412
+ lines.push(encodeErrorRule(flatNet, property, mVars, sinkPlaces, envInject));
1413
+ lines.push("");
1414
+ lines.push("(assert (not Error))");
1415
+ lines.push("(check-sat)");
1416
+ if (produceProofs) lines.push("(get-proof)");
1417
+ lines.push("(get-model)");
1418
+ return { smt2: lines.join("\n"), placeCount: P };
1419
+ }
1420
+ function resolveEnvInjection(flatNet) {
1421
+ const out = [];
1088
1422
  for (const [name, bound] of flatNet.environmentInjection) {
1089
- const idx = flatNet.placeIndex.get(name);
1090
- if (idx == null) continue;
1091
- encodeInjectionRule(ctx, fp, reachable, idx, bound, P);
1423
+ const pid = flatNet.placeIndex.get(name);
1424
+ if (pid != null) out.push({ pid, bound });
1092
1425
  }
1093
- encodeErrorRule(ctx, fp, reachable, error, flatNet, property, sinkPlaces, P);
1094
- return {
1095
- errorExpr: error.call(),
1096
- reachableDecl: reachable
1097
- };
1426
+ out.sort((a, b) => a.pid - b.pid);
1427
+ return out;
1098
1428
  }
1099
- function encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P) {
1100
- const Int = ctx.Int;
1101
- const mVars = [];
1102
- const mPrimeVars = [];
1103
- for (let i = 0; i < P; i++) {
1104
- mVars.push(Int.const(`m${i}`));
1105
- mPrimeVars.push(Int.const(`mp${i}`));
1106
- }
1107
- const reachBody = reachable.call(...mVars);
1108
- const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
1109
- const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
1110
- let nonNeg = ctx.Bool.val(true);
1111
- for (let i = 0; i < P; i++) {
1112
- nonNeg = ctx.And(nonNeg, mPrimeVars[i].ge(0));
1113
- }
1114
- const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
1115
- let envBounds = ctx.Bool.val(true);
1116
- for (const [name, bound] of flatNet.environmentBounds) {
1117
- const idx = flatNet.placeIndex.get(name);
1118
- if (idx != null) {
1119
- envBounds = ctx.And(envBounds, mPrimeVars[idx].le(bound));
1120
- }
1429
+ function envBounds(flatNet) {
1430
+ const out = [];
1431
+ for (const [name, max] of flatNet.environmentBounds) {
1432
+ const pid = flatNet.placeIndex.get(name);
1433
+ if (pid != null) out.push([pid, max]);
1121
1434
  }
1122
- const body = ctx.And(reachBody, enabled, fireRelation, nonNeg, invConstraints, envBounds);
1123
- const head = reachable.call(...mPrimeVars);
1124
- const allVars = [...mVars, ...mPrimeVars];
1125
- const rule = ctx.Implies(body, head);
1126
- const qRule = ctx.ForAll(allVars, rule);
1127
- fp.addRule(qRule, `t_${ft.name}`);
1435
+ out.sort((a, b) => a[0] - b[0]);
1436
+ return out;
1128
1437
  }
1129
- function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
1130
- const Int = ctx.Int;
1131
- const mVars = [];
1132
- const mPrimeVars = [];
1438
+ function ints(n) {
1439
+ return new Array(n).fill("Int");
1440
+ }
1441
+ function vars(P, suffix) {
1442
+ const out = [];
1443
+ for (let i = 0; i < P; i++) out.push(`m${i}${suffix}`);
1444
+ return out;
1445
+ }
1446
+ function quantified(names) {
1447
+ return names.map((v) => `(${v} Int)`).join(" ");
1448
+ }
1449
+ function firingConditions(flatNet, ft, mVars, mpVars) {
1450
+ const P = flatNet.places.length;
1451
+ const conditions = [];
1133
1452
  for (let i = 0; i < P; i++) {
1134
- mVars.push(Int.const(`m${i}`));
1135
- mPrimeVars.push(Int.const(`mp${i}`));
1453
+ if (ft.preVector[i] > 0) conditions.push(`(>= ${mVars[i]} ${ft.preVector[i]})`);
1136
1454
  }
1137
- const reachBody = reachable.call(...mVars);
1138
- let fire = ctx.Bool.val(true);
1455
+ for (const inh of ft.inhibitorPlaces) conditions.push(`(= ${mVars[inh]} 0)`);
1456
+ for (const rd of ft.readPlaces) conditions.push(`(>= ${mVars[rd]} 1)`);
1139
1457
  for (let i = 0; i < P; i++) {
1140
- if (i === idx) {
1141
- fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i].add(1)));
1458
+ if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
1459
+ conditions.push(`(= ${mpVars[i]} ${ft.postVector[i]})`);
1142
1460
  } else {
1143
- fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i]));
1461
+ const delta = ft.postVector[i] - ft.preVector[i];
1462
+ if (delta > 0) conditions.push(`(= ${mpVars[i]} (+ ${mVars[i]} ${delta}))`);
1463
+ else if (delta < 0) conditions.push(`(= ${mpVars[i]} (- ${mVars[i]} ${-delta}))`);
1464
+ else conditions.push(`(= ${mpVars[i]} ${mVars[i]})`);
1144
1465
  }
1145
1466
  }
1146
- const guard = bound === null ? ctx.Bool.val(true) : mVars[idx].lt(bound);
1147
- const body = ctx.And(reachBody, guard, fire);
1148
- const head = reachable.call(...mPrimeVars);
1149
- const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
1150
- fp.addRule(qRule, `env_inject_${idx}`);
1467
+ for (let i = 0; i < P; i++) conditions.push(`(>= ${mpVars[i]} 0)`);
1468
+ return conditions;
1151
1469
  }
1152
- function injectedEnvIndices(flatNet) {
1153
- const out = /* @__PURE__ */ new Map();
1154
- for (const [name, bound] of flatNet.environmentInjection) {
1155
- const idx = flatNet.placeIndex.get(name);
1156
- if (idx != null) out.set(idx, bound);
1470
+ function invariantConditions(invariants, names) {
1471
+ const conditions = [];
1472
+ for (const inv of invariants) {
1473
+ const terms = [...inv.support].sort((a, b) => a - b).map((i) => `(* ${inv.weights[i]} ${names[i]})`);
1474
+ if (terms.length === 0) continue;
1475
+ const sum = terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
1476
+ conditions.push(`(= ${sum} ${inv.constant})`);
1157
1477
  }
1158
- return out;
1478
+ return conditions;
1159
1479
  }
1160
- function encodeEnabled(ctx, ft, flatNet, mVars, P, relaxEnv = false) {
1161
- let result = ctx.Bool.val(true);
1162
- const envInj = relaxEnv ? injectedEnvIndices(flatNet) : void 0;
1163
- for (let p = 0; p < P; p++) {
1164
- const pre = ft.preVector[p];
1165
- if (pre <= 0) continue;
1166
- if (envInj?.has(p)) {
1167
- const bound = envInj.get(p);
1168
- if (bound !== null && pre > bound) return ctx.Bool.val(false);
1169
- continue;
1170
- }
1171
- result = ctx.And(result, mVars[p].ge(pre));
1480
+ function envBoundConditions(flatNet, mpVars) {
1481
+ return envBounds(flatNet).map(([pid, max]) => `(<= ${mpVars[pid]} ${max})`);
1482
+ }
1483
+ function injectionConditions(P, pid, bound, mVars, mpVars) {
1484
+ const conditions = [];
1485
+ if (bound != null) conditions.push(`(< ${mVars[pid]} ${bound})`);
1486
+ for (let i = 0; i < P; i++) {
1487
+ if (i === pid) conditions.push(`(= ${mpVars[i]} (+ ${mVars[i]} 1))`);
1488
+ else conditions.push(`(= ${mpVars[i]} ${mVars[i]})`);
1172
1489
  }
1173
- for (const p of ft.readPlaces) {
1174
- if (envInj?.has(p)) {
1175
- const bound = envInj.get(p);
1176
- if (bound !== null && bound < 1) return ctx.Bool.val(false);
1177
- continue;
1178
- }
1179
- result = ctx.And(result, mVars[p].ge(1));
1490
+ return conditions;
1491
+ }
1492
+ function encodeTransitionRule(flatNet, ft, mVars, mpVars, invariants) {
1493
+ const conditions = [`(Reachable ${mVars.join(" ")})`];
1494
+ conditions.push(...firingConditions(flatNet, ft, mVars, mpVars));
1495
+ conditions.push(...invariantConditions(invariants, mpVars));
1496
+ conditions.push(...envBoundConditions(flatNet, mpVars));
1497
+ const body = `(and ${conditions.join("\n ")})`;
1498
+ return `(assert (forall (${quantified([...mVars, ...mpVars])})
1499
+ (=> ${body}
1500
+ (Reachable ${mpVars.join(" ")}))))`;
1501
+ }
1502
+ function encodeInjectionRule(P, pid, bound, mVars, mpVars) {
1503
+ const conditions = [`(Reachable ${mVars.join(" ")})`];
1504
+ conditions.push(...injectionConditions(P, pid, bound, mVars, mpVars));
1505
+ const body = `(and ${conditions.join("\n ")})`;
1506
+ return `(assert (forall (${quantified([...mVars, ...mpVars])})
1507
+ (=> ${body}
1508
+ (Reachable ${mpVars.join(" ")}))))`;
1509
+ }
1510
+ function conjoin(conditions) {
1511
+ if (conditions.length === 0) return "true";
1512
+ if (conditions.length === 1) return conditions[0];
1513
+ return `(and ${conditions.join(" ")})`;
1514
+ }
1515
+ function encodeStepRelationSmt2(flatNet) {
1516
+ const P = flatNet.places.length;
1517
+ const mVars = vars(P, "");
1518
+ const mpVars = vars(P, "p");
1519
+ const disjuncts = [];
1520
+ for (const ft of flatNet.transitions) {
1521
+ const conditions = firingConditions(flatNet, ft, mVars, mpVars);
1522
+ conditions.push(...envBoundConditions(flatNet, mpVars));
1523
+ disjuncts.push(conjoin(conditions));
1180
1524
  }
1181
- for (const p of ft.inhibitorPlaces) {
1182
- result = ctx.And(result, mVars[p].eq(0));
1525
+ for (const inj of resolveEnvInjection(flatNet)) {
1526
+ disjuncts.push(conjoin(injectionConditions(P, inj.pid, inj.bound, mVars, mpVars)));
1183
1527
  }
1184
- for (let p = 0; p < P; p++) {
1185
- result = ctx.And(result, mVars[p].ge(0));
1186
- }
1187
- return result;
1528
+ if (disjuncts.length === 0) return "false";
1529
+ if (disjuncts.length === 1) return disjuncts[0];
1530
+ return `(or ${disjuncts.join("\n ")})`;
1188
1531
  }
1189
- function encodeFire(ctx, ft, _flatNet, mVars, mPrimeVars, P) {
1190
- let result = ctx.Bool.val(true);
1191
- for (let p = 0; p < P; p++) {
1192
- const isReset = ft.resetPlaces.includes(p);
1193
- if (isReset || ft.consumeAll[p]) {
1194
- result = ctx.And(result, mPrimeVars[p].eq(ft.postVector[p]));
1195
- } else {
1196
- const delta = ft.postVector[p] - ft.preVector[p];
1197
- if (delta === 0) {
1198
- result = ctx.And(result, mPrimeVars[p].eq(mVars[p]));
1199
- } else {
1200
- result = ctx.And(result, mPrimeVars[p].eq(mVars[p].add(delta)));
1201
- }
1202
- }
1203
- }
1204
- return result;
1532
+ function encodeErrorRule(flatNet, property, mVars, sinkPlaces, envInject) {
1533
+ const violation = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject);
1534
+ return `(assert (forall (${quantified(mVars)})
1535
+ (=> (and (Reachable ${mVars.join(" ")}) ${violation})
1536
+ Error)))`;
1205
1537
  }
1206
- function encodeErrorRule(ctx, fp, reachable, error, flatNet, property, sinkPlaces, P) {
1207
- const Int = ctx.Int;
1208
- const mVars = [];
1209
- for (let i = 0; i < P; i++) {
1210
- mVars.push(Int.const(`em${i}`));
1538
+ function indexOrdered(flatNet, places) {
1539
+ const idx = /* @__PURE__ */ new Set();
1540
+ for (const place of places) {
1541
+ const i = flatNet.placeIndex.get(place.name);
1542
+ if (i != null) idx.add(i);
1211
1543
  }
1212
- const reachBody = reachable.call(...mVars);
1213
- const violation = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
1214
- const head = error.call();
1215
- const body = ctx.And(reachBody, violation);
1216
- const rule = ctx.Implies(body, head);
1217
- const qRule = ctx.ForAll(mVars, rule);
1218
- fp.addRule(qRule, `error_${property.type}`);
1544
+ return [...idx].sort((a, b) => a - b);
1219
1545
  }
1220
- function encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P) {
1546
+ function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject) {
1221
1547
  switch (property.type) {
1222
- case "deadlock-free": {
1223
- const deadlock = encodeDeadlock(ctx, flatNet, mVars, P);
1224
- if (sinkPlaces.size > 0) {
1225
- let notAtSink = ctx.Bool.val(true);
1226
- for (const sink of sinkPlaces) {
1227
- const idx = flatNetIndexOf(flatNet, sink);
1228
- if (idx >= 0) {
1229
- notAtSink = ctx.And(notAtSink, mVars[idx].eq(0));
1230
- }
1231
- }
1232
- return ctx.And(deadlock, notAtSink);
1233
- }
1234
- return deadlock;
1235
- }
1548
+ case "deadlock-free":
1549
+ return encodeDeadlock(flatNet, mVars, sinkPlaces, envInject);
1236
1550
  case "mutual-exclusion": {
1237
- const idx1 = flatNetIndexOf(flatNet, property.p1);
1238
- const idx2 = flatNetIndexOf(flatNet, property.p2);
1239
- if (idx1 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p1.name}`);
1240
- if (idx2 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p2.name}`);
1241
- return ctx.And(mVars[idx1].ge(1), mVars[idx2].ge(1));
1242
- }
1243
- case "place-bound": {
1244
- const idx = flatNetIndexOf(flatNet, property.place);
1245
- if (idx < 0) throw new Error(`PlaceBound references unknown place: ${property.place.name}`);
1246
- return mVars[idx].gt(property.bound);
1551
+ const conditions = indexOrdered(flatNet, [property.p1, property.p2]).map((i) => `(>= ${mVars[i]} 1)`);
1552
+ return conditions.length === 0 ? "false" : `(and ${conditions.join(" ")})`;
1247
1553
  }
1554
+ case "place-bound":
1248
1555
  case "branch-place-bound": {
1249
- const idx = flatNetIndexOf(flatNet, property.place);
1250
- if (idx < 0) throw new Error(`BranchPlaceBound references unknown place: ${property.place.name}`);
1251
- return mVars[idx].gt(property.bound);
1556
+ const pid = flatNet.placeIndex.get(property.place.name);
1557
+ return pid == null ? "false" : `(> ${mVars[pid]} ${property.bound})`;
1558
+ }
1559
+ case "unreachable": {
1560
+ const conditions = indexOrdered(flatNet, property.places).map((i) => `(>= ${mVars[i]} 1)`);
1561
+ return conditions.length === 0 ? "false" : `(and ${conditions.join(" ")})`;
1252
1562
  }
1253
1563
  case "joined-or-dead-lettered": {
1254
- const idx = flatNetIndexOf(flatNet, property.pending);
1255
- if (idx < 0) return ctx.Bool.val(false);
1256
- const deadlock = encodeDeadlock(ctx, flatNet, mVars, P);
1257
- return ctx.And(deadlock, mVars[idx].ge(1));
1564
+ const deadlock = encodeDeadlock(flatNet, mVars, sinkPlaces, envInject);
1565
+ const pid = flatNet.placeIndex.get(property.pending.name);
1566
+ return pid == null ? "false" : `(and ${deadlock} (>= ${mVars[pid]} 1))`;
1258
1567
  }
1259
- case "unreachable": {
1260
- let allMarked = ctx.Bool.val(true);
1261
- for (const place of property.places) {
1262
- const idx = flatNetIndexOf(flatNet, place);
1263
- if (idx >= 0) {
1264
- allMarked = ctx.And(allMarked, mVars[idx].ge(1));
1568
+ }
1569
+ }
1570
+ function encodeDeadlock(flatNet, mVars, sinkPlaces, envInject) {
1571
+ const envBound = /* @__PURE__ */ new Map();
1572
+ for (const inj of envInject) envBound.set(inj.pid, inj.bound);
1573
+ const disabledConditions = [];
1574
+ for (const ft of flatNet.transitions) {
1575
+ const disableReasons = [];
1576
+ let permanentlyDisabled = false;
1577
+ for (let i = 0; i < flatNet.places.length; i++) {
1578
+ if (ft.preVector[i] > 0) {
1579
+ if (envBound.has(i)) {
1580
+ const k = envBound.get(i);
1581
+ if (k != null && ft.preVector[i] > k) permanentlyDisabled = true;
1582
+ continue;
1265
1583
  }
1584
+ disableReasons.push(`(< ${mVars[i]} ${ft.preVector[i]})`);
1266
1585
  }
1267
- return allMarked;
1268
1586
  }
1587
+ for (const inh of ft.inhibitorPlaces) disableReasons.push(`(> ${mVars[inh]} 0)`);
1588
+ for (const rd of ft.readPlaces) {
1589
+ if (envBound.has(rd)) {
1590
+ const k = envBound.get(rd);
1591
+ if (k != null && k < 1) permanentlyDisabled = true;
1592
+ continue;
1593
+ }
1594
+ disableReasons.push(`(< ${mVars[rd]} 1)`);
1595
+ }
1596
+ if (permanentlyDisabled) {
1597
+ disabledConditions.push("true");
1598
+ continue;
1599
+ }
1600
+ if (disableReasons.length === 0) return "false";
1601
+ disabledConditions.push(`(or ${disableReasons.join(" ")})`);
1602
+ }
1603
+ for (const pid of indexOrdered(flatNet, sinkPlaces)) {
1604
+ disabledConditions.push(`(= ${mVars[pid]} 0)`);
1269
1605
  }
1606
+ return disabledConditions.length === 0 ? "true" : `(and ${disabledConditions.join("\n ")})`;
1270
1607
  }
1271
- function encodeDeadlock(ctx, flatNet, mVars, P) {
1272
- let deadlock = ctx.Bool.val(true);
1273
- for (const ft of flatNet.transitions) {
1274
- const enabled = encodeEnabled(
1275
- ctx,
1276
- ft,
1277
- flatNet,
1278
- mVars,
1279
- P,
1280
- /* relaxEnv */
1281
- true
1282
- );
1283
- deadlock = ctx.And(deadlock, ctx.Not(enabled));
1608
+ function injectionMap(flatNet) {
1609
+ const out = /* @__PURE__ */ new Map();
1610
+ for (const inj of resolveEnvInjection(flatNet)) out.set(inj.pid, inj.bound);
1611
+ return out;
1612
+ }
1613
+
1614
+ // src/verification/z3/certificate-checker.ts
1615
+ var VC_LABELS = ["initiation (VC1)", "consecution (VC2)", "safety (VC3)"];
1616
+ async function checkCertificate(certificate, flatNet, initialMarking, property, invariants, sinkPlaces, solver, timeoutMs) {
1617
+ if (certificate == null) {
1618
+ return {
1619
+ type: "unavailable",
1620
+ reason: "no inductive invariant (define-fun block) could be extracted from the z3 model",
1621
+ invariant: null
1622
+ };
1623
+ }
1624
+ const shape = shapeFailure(flatNet, invariants);
1625
+ if (shape != null) return { type: "unavailable", reason: shape, invariant: certificate };
1626
+ if (!certificate.includes("(define-fun Reachable ") && !certificate.includes("(define-fun |Reachable| ")) {
1627
+ return { type: "unavailable", reason: "certificate does not define Reachable", invariant: certificate };
1628
+ }
1629
+ const vcs = buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants);
1630
+ let results;
1631
+ try {
1632
+ results = await runVcScript(script(vcs), timeoutMs, solver);
1633
+ } catch (e) {
1634
+ return { type: "unavailable", reason: String(e?.message ?? e), invariant: certificate };
1635
+ }
1636
+ for (let i = 0; i < results.length; i++) {
1637
+ if (results[i] !== "unsat") {
1638
+ const detail = await detailFor(vcs, i, results[i], flatNet, timeoutMs, solver);
1639
+ return { type: "failed", vc: VC_LABELS[i], detail, invariant: certificate };
1640
+ }
1284
1641
  }
1285
- return deadlock;
1642
+ return { type: "passed", invariant: certificate };
1286
1643
  }
1287
- function encodeInvariantConstraints(ctx, invariants, mVars, P) {
1288
- let result = ctx.Bool.val(true);
1644
+ function vcScript(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
1645
+ return script(buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants));
1646
+ }
1647
+ function shapeFailure(flatNet, invariants) {
1648
+ const P = flatNet.places.length;
1289
1649
  for (const inv of invariants) {
1290
- let sum = ctx.Int.val(0);
1291
- for (const idx of inv.support) {
1292
- if (idx < P) {
1293
- sum = sum.add(mVars[idx].mul(inv.weights[idx]));
1294
- }
1650
+ if (inv.weights.length !== P) {
1651
+ return `P-invariant has ${inv.weights.length} weights for a ${P}-place net`;
1652
+ }
1653
+ for (const pid of inv.support) {
1654
+ if (pid >= P || pid < 0) return `P-invariant support names place index ${pid} in a ${P}-place net`;
1295
1655
  }
1296
- result = ctx.And(result, sum.eq(inv.constant));
1297
1656
  }
1298
- return result;
1657
+ return null;
1658
+ }
1659
+ var VcFailure = class extends Error {
1660
+ };
1661
+ async function runVcScript(text, timeoutMs, solver) {
1662
+ const reply = await runZ3Text(solver, text, "certificate", timeoutMs, []);
1663
+ const budget = timeoutBudget(timeoutMs);
1664
+ const err = errorLine(reply.stderr);
1665
+ if (err != null) throw new VcFailure(`z3 reported an error on stderr: ${err}`);
1666
+ if (timeoutLine(reply.stdout)) {
1667
+ throw new VcFailure(`z3 hard timeout after ${hardTimeoutSecs(budget)}s while checking the certificate`);
1668
+ }
1669
+ if (reply.exit.kind === "killed") {
1670
+ throw new VcFailure(`z3 did not exit within ${watchdogMs(budget)} ms while checking the certificate and was killed`);
1671
+ }
1672
+ const results = parseVcResults(reply.stdout);
1673
+ if (!replySucceeded(reply)) {
1674
+ const status = reply.exit.kind === "exited" ? `exit status: ${reply.exit.code}` : "the watchdog kill";
1675
+ throw new VcFailure(`z3 exited with ${status} after answering [${results.join(", ")}]`);
1676
+ }
1677
+ return results;
1678
+ }
1679
+ function parseVcResults(stdout) {
1680
+ const err = errorLine(stdout);
1681
+ if (err != null) throw new VcFailure(`z3 error while checking the certificate: ${err}`);
1682
+ if (timeoutLine(stdout)) throw new VcFailure("z3 hard timeout while checking the certificate");
1683
+ const results = stdout.split("\n").map((l) => l.trim()).filter((l) => l === "sat" || l === "unsat" || l === "unknown");
1684
+ if (results.length !== 3) {
1685
+ throw new VcFailure(`expected 3 VC answers from z3, got ${results.length}: [${results.join(", ")}]`);
1686
+ }
1687
+ return results;
1688
+ }
1689
+ function buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
1690
+ const P = flatNet.places.length;
1691
+ const mVars = [];
1692
+ const mpVars = [];
1693
+ for (let i = 0; i < P; i++) {
1694
+ mVars.push(`m${i}`);
1695
+ mpVars.push(`m${i}p`);
1696
+ }
1697
+ const prelude = [
1698
+ "; IC3/PDR certificate check (plain SMT-LIB2, not HORN):",
1699
+ "; each VC below must be unsat for the certificate to stand.",
1700
+ certificate,
1701
+ ""
1702
+ ];
1703
+ for (const v of mVars) prelude.push(`(declare-const ${v} Int)`);
1704
+ for (const v of mpVars) prelude.push(`(declare-const ${v} Int)`);
1705
+ const m0 = [];
1706
+ for (let i = 0; i < P; i++) m0.push(String(initialMarking.tokens(flatNet.places[i])));
1707
+ const vc1 = [`(assert (not ${candidate(m0, invariants)}))`];
1708
+ const nonNegative = mVars.map((v) => `(assert (>= ${v} 0))`);
1709
+ const step = encodeStepRelationSmt2(flatNet);
1710
+ const vc2 = [
1711
+ ...nonNegative,
1712
+ `(assert ${candidate(mVars, invariants)})`,
1713
+ `(assert ${step})`,
1714
+ `(assert (not ${candidate(mpVars, invariants)}))`
1715
+ ];
1716
+ const bad = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, resolveEnvInjection(flatNet));
1717
+ const vc3 = [...nonNegative, `(assert ${candidate(mVars, invariants)})`, `(assert ${bad})`];
1718
+ return { prelude, asserts: [vc1, vc2, vc3] };
1719
+ }
1720
+ function script(vcs) {
1721
+ const lines = [...vcs.prelude];
1722
+ for (let i = 0; i < vcs.asserts.length; i++) {
1723
+ lines.push("");
1724
+ lines.push(`; VC${i + 1} ${VC_LABELS[i]}`);
1725
+ lines.push("(push)");
1726
+ lines.push(...vcs.asserts[i]);
1727
+ lines.push("(check-sat)");
1728
+ lines.push("(pop)");
1729
+ }
1730
+ return lines.join("\n");
1731
+ }
1732
+ async function detailFor(vcs, i, answer, flatNet, timeoutMs, solver) {
1733
+ const lines = ["(set-option :produce-models true)", ...vcs.prelude, ...vcs.asserts[i], "(check-sat)"];
1734
+ lines.push(answer === "sat" ? "(get-model)" : "(get-info :reason-unknown)");
1735
+ let reply = "";
1736
+ try {
1737
+ reply = (await runZ3Text(solver, lines.join("\n"), "certificate-detail", timeoutMs, [])).stdout;
1738
+ } catch {
1739
+ reply = "";
1740
+ }
1741
+ if (answer === "sat") {
1742
+ const w = witness(reply, flatNet);
1743
+ return w == null ? "solver returned SATISFIABLE" : `solver returned SATISFIABLE (witness: ${w})`;
1744
+ }
1745
+ const r = reasonUnknown(reply);
1746
+ return r == null ? "solver returned UNKNOWN" : `solver returned UNKNOWN (${r})`;
1747
+ }
1748
+ function witness(model, flatNet) {
1749
+ const parts = [];
1750
+ for (let i = 0; i < flatNet.places.length; i++) {
1751
+ const needle = `(define-fun m${i} () Int`;
1752
+ const at = model.indexOf(needle);
1753
+ if (at < 0) continue;
1754
+ const rest = model.slice(at + needle.length).trimStart();
1755
+ let value;
1756
+ if (rest.startsWith("(")) {
1757
+ const end = sexprEnd(rest, 0);
1758
+ if (end < 0) continue;
1759
+ value = rest.slice(1, end - 1).trim().split(/\s+/).join("");
1760
+ } else {
1761
+ let end = 0;
1762
+ while (end < rest.length && !/\s/.test(rest[end]) && rest[end] !== ")") end++;
1763
+ if (end === 0) continue;
1764
+ value = rest.slice(0, end);
1765
+ }
1766
+ parts.push(`${flatNet.places[i].name}=${value}`);
1767
+ }
1768
+ return parts.length === 0 ? null : parts.join(", ");
1769
+ }
1770
+ function reasonUnknown(reply) {
1771
+ const at = reply.indexOf(":reason-unknown");
1772
+ if (at < 0) return null;
1773
+ const rest = reply.slice(at + ":reason-unknown".length).trimStart();
1774
+ const end = rest.indexOf(")");
1775
+ if (end < 0) return null;
1776
+ let reason = rest.slice(0, end).trim();
1777
+ if (reason.startsWith('"') && reason.endsWith('"') && reason.length >= 2) reason = reason.slice(1, -1);
1778
+ reason = reason.trim();
1779
+ return reason === "" ? null : reason;
1780
+ }
1781
+ function candidate(names, invariants) {
1782
+ return conjoin([`(Reachable ${names.join(" ")})`, ...invariantConditions(invariants, names)]);
1299
1783
  }
1300
1784
 
1301
1785
  // src/verification/analysis/dbm.ts
@@ -1831,66 +2315,321 @@ function consumeFromPlace(builder, place, count, environmentPlaces, environmentM
1831
2315
  }
1832
2316
 
1833
2317
  // src/verification/z3/counterexample-decoder.ts
1834
- function decode(ctx, answer, flatNet) {
1835
- const trace = [];
1836
- const transitions = [];
1837
- if (answer == null) {
1838
- return { trace, transitions };
2318
+ function decode(answer, flatNet) {
2319
+ const states = decodeStateSet(answer, flatNet);
2320
+ return { states, note: states.size === 0 ? "no ground Reachable states in the z3 proof" : null };
2321
+ }
2322
+ function decodeStateSet(answer, flatNet) {
2323
+ const byKey = /* @__PURE__ */ new Map();
2324
+ const P = flatNet.places.length;
2325
+ for (const head of ["(Reachable", "(|Reachable|"]) {
2326
+ let from = 0;
2327
+ for (; ; ) {
2328
+ const start = answer.indexOf(head, from);
2329
+ if (start < 0) break;
2330
+ from = start + head.length;
2331
+ if (head === "(Reachable") {
2332
+ const next = answer[from];
2333
+ if (next == null || !(/\s/.test(next) || next === ")")) continue;
2334
+ }
2335
+ const end = sexprEnd(answer, start);
2336
+ if (end < 0) break;
2337
+ const inner = answer.slice(start + head.length, end - 1);
2338
+ const args = parseGroundIntArgs(inner);
2339
+ if (args != null && args.length === P) {
2340
+ const marking = toMarking(args, flatNet);
2341
+ const key = marking.toString();
2342
+ if (!byKey.has(key)) byKey.set(key, marking);
2343
+ }
2344
+ }
1839
2345
  }
1840
- try {
1841
- extractTrace(ctx, answer, flatNet, trace, transitions);
1842
- } catch {
2346
+ return new Set(byKey.values());
2347
+ }
2348
+ function toMarking(args, flatNet) {
2349
+ const builder = MarkingState.builder();
2350
+ for (let i = 0; i < args.length; i++) {
2351
+ if (args[i] > 0) builder.tokens(flatNet.places[i], args[i]);
2352
+ }
2353
+ return builder.build();
2354
+ }
2355
+ function parseGroundIntArgs(inner) {
2356
+ const args = [];
2357
+ let rest = inner.trimStart();
2358
+ while (rest !== "") {
2359
+ if (rest.startsWith("(")) {
2360
+ const stripped = rest.slice(1);
2361
+ const close = stripped.indexOf(")");
2362
+ if (close < 0) return null;
2363
+ const body = stripped.slice(0, close);
2364
+ if (body.includes("(")) return null;
2365
+ const trimmed = body.trim();
2366
+ if (!trimmed.startsWith("-")) return null;
2367
+ const n = parseInt64(trimmed.slice(1).trim());
2368
+ if (n == null) return null;
2369
+ args.push(-n);
2370
+ rest = stripped.slice(close + 1).trimStart();
2371
+ } else {
2372
+ let tokenEnd = rest.length;
2373
+ for (let i = 0; i < rest.length; i++) {
2374
+ const c = rest[i];
2375
+ if (/\s/.test(c) || c === "(" || c === ")") {
2376
+ tokenEnd = i;
2377
+ break;
2378
+ }
2379
+ }
2380
+ const n = parseInt64(rest.slice(0, tokenEnd));
2381
+ if (n == null) return null;
2382
+ args.push(n);
2383
+ rest = rest.slice(tokenEnd).trimStart();
2384
+ }
2385
+ }
2386
+ return args;
2387
+ }
2388
+ function parseInt64(token) {
2389
+ return /^-?\d+$/.test(token) ? Number(token) : null;
2390
+ }
2391
+
2392
+ // src/verification/encoding/flat-net.ts
2393
+ function flatNetPlaceCount(net) {
2394
+ return net.places.length;
2395
+ }
2396
+ function flatNetTransitionCount(net) {
2397
+ return net.transitions.length;
2398
+ }
2399
+ function flatNetIndexOf(net, place) {
2400
+ return net.placeIndex.get(place.name) ?? -1;
2401
+ }
2402
+
2403
+ // src/verification/z3/abstract-replayer.ts
2404
+ function stepName(step) {
2405
+ return step.kind === "fire" ? step.transition : `inject(${step.place})`;
2406
+ }
2407
+ function stateKey(state) {
2408
+ return state.join(",");
2409
+ }
2410
+ function vectorize(marking, flatNet) {
2411
+ return flatNet.places.map((p) => marking.tokens(p));
2412
+ }
2413
+ function toMarkingState(state, flatNet) {
2414
+ const builder = MarkingState.builder();
2415
+ for (let i = 0; i < flatNet.places.length; i++) {
2416
+ if (state[i] > 0) builder.tokens(flatNet.places[i], state[i]);
2417
+ }
2418
+ return builder.build();
2419
+ }
2420
+ function enabledA(state, ft) {
2421
+ const P = state.length;
2422
+ for (let p = 0; p < P; p++) {
2423
+ if (ft.preVector[p] > 0 && state[p] < ft.preVector[p]) return false;
2424
+ }
2425
+ for (const p of ft.readPlaces) {
2426
+ if (state[p] < 1) return false;
2427
+ }
2428
+ for (const p of ft.inhibitorPlaces) {
2429
+ if (state[p] !== 0) return false;
2430
+ }
2431
+ return true;
2432
+ }
2433
+ function fireIndexed(state, ft, resets) {
2434
+ const P = state.length;
2435
+ const next = new Array(P);
2436
+ for (let p = 0; p < P; p++) {
2437
+ if (resets.has(p) || ft.consumeAll[p]) {
2438
+ next[p] = ft.postVector[p];
2439
+ } else {
2440
+ next[p] = state[p] - ft.preVector[p] + ft.postVector[p];
2441
+ }
2442
+ }
2443
+ return next;
2444
+ }
2445
+ function injectA(state, idx) {
2446
+ const next = [...state];
2447
+ next[idx] = next[idx] + 1;
2448
+ return next;
2449
+ }
2450
+ function buildIndex(flatNet) {
2451
+ const resetSets = flatNet.transitions.map((ft) => new Set(ft.resetPlaces));
2452
+ const envInj = /* @__PURE__ */ new Map();
2453
+ for (const [name, bound] of flatNet.environmentInjection) {
2454
+ const idx = flatNet.placeIndex.get(name);
2455
+ if (idx != null) envInj.set(idx, bound);
2456
+ }
2457
+ const envCaps = [];
2458
+ for (const [name, cap] of flatNet.environmentBounds) {
2459
+ const idx = flatNet.placeIndex.get(name);
2460
+ if (idx != null) envCaps.push([idx, cap]);
2461
+ }
2462
+ return { flatNet, resetSets, envInj, envCaps };
2463
+ }
2464
+ function withinEnvBounds(index, state) {
2465
+ for (const [idx, cap] of index.envCaps) {
2466
+ if (state[idx] > cap) return false;
2467
+ }
2468
+ return true;
2469
+ }
2470
+ function successorsIndexed(index, state) {
2471
+ const out = [];
2472
+ const transitions = index.flatNet.transitions;
2473
+ for (let t = 0; t < transitions.length; t++) {
2474
+ const ft = transitions[t];
2475
+ if (!enabledA(state, ft)) continue;
2476
+ const next = fireIndexed(state, ft, index.resetSets[t]);
2477
+ if (!withinEnvBounds(index, next)) continue;
2478
+ out.push({ state: next, step: { kind: "fire", transition: ft.name } });
2479
+ }
2480
+ for (const [name, bound] of index.flatNet.environmentInjection) {
2481
+ const idx = index.flatNet.placeIndex.get(name);
2482
+ if (idx == null) continue;
2483
+ if (bound === null || state[idx] < bound) {
2484
+ out.push({ state: injectA(state, idx), step: { kind: "inject", place: name } });
2485
+ }
2486
+ }
2487
+ return out;
2488
+ }
2489
+ function enabledRelaxEnv(state, ft, envInj) {
2490
+ const P = state.length;
2491
+ for (let p = 0; p < P; p++) {
2492
+ const pre = ft.preVector[p];
2493
+ if (pre <= 0) continue;
2494
+ if (envInj.has(p)) {
2495
+ const bound = envInj.get(p);
2496
+ if (bound !== null && pre > bound) return false;
2497
+ continue;
2498
+ }
2499
+ if (state[p] < pre) return false;
2500
+ }
2501
+ for (const p of ft.readPlaces) {
2502
+ if (envInj.has(p)) {
2503
+ const bound = envInj.get(p);
2504
+ if (bound !== null && bound < 1) return false;
2505
+ continue;
2506
+ }
2507
+ if (state[p] < 1) return false;
1843
2508
  }
1844
- return { trace, transitions };
2509
+ for (const p of ft.inhibitorPlaces) {
2510
+ if (state[p] !== 0) return false;
2511
+ }
2512
+ return true;
1845
2513
  }
1846
- function extractTrace(ctx, expr, flatNet, trace, transitions) {
1847
- if (expr == null) return;
1848
- if (!ctx.isApp(expr)) return;
1849
- let name;
1850
- try {
1851
- const decl = expr.decl();
1852
- name = String(decl.name());
1853
- } catch {
1854
- return;
2514
+ function isDeadlockA(index, state) {
2515
+ for (const ft of index.flatNet.transitions) {
2516
+ if (enabledRelaxEnv(state, ft, index.envInj)) return false;
1855
2517
  }
1856
- const P = flatNet.places.length;
1857
- if (name === "Reachable") {
1858
- const numArgs = expr.numArgs();
1859
- if (numArgs === P) {
1860
- const marking = extractMarking(ctx, expr, flatNet);
1861
- if (marking != null) {
1862
- trace.push(marking);
2518
+ return true;
2519
+ }
2520
+ function satisfiesBadIndexed(index, state, property, sinkPlaces) {
2521
+ const flatNet = index.flatNet;
2522
+ switch (property.type) {
2523
+ case "deadlock-free": {
2524
+ if (!isDeadlockA(index, state)) return false;
2525
+ for (const sink of sinkPlaces) {
2526
+ const idx = flatNetIndexOf(flatNet, sink);
2527
+ if (idx >= 0 && state[idx] > 0) return false;
1863
2528
  }
2529
+ return true;
1864
2530
  }
1865
- }
1866
- try {
1867
- const numArgs = expr.numArgs();
1868
- for (let i = 0; i < numArgs; i++) {
1869
- const child = expr.arg(i);
1870
- extractTrace(ctx, child, flatNet, trace, transitions);
2531
+ case "mutual-exclusion": {
2532
+ const idx1 = flatNetIndexOf(flatNet, property.p1);
2533
+ const idx2 = flatNetIndexOf(flatNet, property.p2);
2534
+ if (idx1 < 0 || idx2 < 0) return false;
2535
+ return state[idx1] >= 1 && state[idx2] >= 1;
2536
+ }
2537
+ case "place-bound":
2538
+ case "branch-place-bound": {
2539
+ const idx = flatNetIndexOf(flatNet, property.place);
2540
+ if (idx < 0) return false;
2541
+ return state[idx] > property.bound;
2542
+ }
2543
+ case "joined-or-dead-lettered": {
2544
+ const idx = flatNetIndexOf(flatNet, property.pending);
2545
+ if (idx < 0) return false;
2546
+ return isDeadlockA(index, state) && state[idx] >= 1;
2547
+ }
2548
+ case "unreachable": {
2549
+ let resolved = 0;
2550
+ for (const p of property.places) {
2551
+ const idx = flatNetIndexOf(flatNet, p);
2552
+ if (idx < 0) continue;
2553
+ resolved++;
2554
+ if (state[idx] < 1) return false;
2555
+ }
2556
+ return resolved > 0;
1871
2557
  }
1872
- } catch {
1873
- }
1874
- if (name.startsWith("t_")) {
1875
- transitions.push(name.substring(2));
1876
2558
  }
1877
2559
  }
1878
- function extractMarking(ctx, reachableApp, flatNet) {
1879
- const P = flatNet.places.length;
1880
- if (reachableApp.numArgs() !== P) return null;
1881
- const builder = MarkingState.builder();
1882
- for (let i = 0; i < P; i++) {
1883
- const arg = reachableApp.arg(i);
1884
- if (ctx.isIntVal(arg)) {
1885
- const tokens = Number(arg.value());
1886
- if (tokens > 0) {
1887
- builder.tokens(flatNet.places[i], tokens);
2560
+ function replayCounterexample(flatNet, initial, decodedStates, property, sinkPlaces, options = {}) {
2561
+ const segmentBudget = options.segmentBudget ?? 3;
2562
+ const nodeBudget = options.nodeBudget ?? 1e4;
2563
+ const anchors = /* @__PURE__ */ new Set();
2564
+ for (const s of decodedStates) anchors.add(stateKey(s));
2565
+ if (anchors.size === 0) {
2566
+ return { kind: "exhausted", reason: "no decoded states to replay", nodesExplored: 0 };
2567
+ }
2568
+ const initKey = stateKey(initial);
2569
+ if (!anchors.has(initKey)) {
2570
+ return {
2571
+ kind: "exhausted",
2572
+ reason: "the initial marking is not among the decoded states",
2573
+ nodesExplored: 0
2574
+ };
2575
+ }
2576
+ const index = buildIndex(flatNet);
2577
+ if (satisfiesBadIndexed(index, initial, property, sinkPlaces)) {
2578
+ return { kind: "confirmed", states: [initial], steps: [], nodesExplored: 1 };
2579
+ }
2580
+ const nodes = [{ state: initial, step: null, parent: -1, segment: 0 }];
2581
+ const bestSegment = /* @__PURE__ */ new Map([[initKey, 0]]);
2582
+ const queue = [0];
2583
+ let truncated = false;
2584
+ for (let head = 0; head < queue.length; head++) {
2585
+ const idx = queue[head];
2586
+ const node = nodes[idx];
2587
+ if (node.segment >= segmentBudget) {
2588
+ truncated = true;
2589
+ continue;
2590
+ }
2591
+ for (const succ of successorsIndexed(index, node.state)) {
2592
+ const key = stateKey(succ.state);
2593
+ const segment = anchors.has(key) ? 0 : node.segment + 1;
2594
+ const prior = bestSegment.get(key);
2595
+ if (prior !== void 0 && prior <= segment) continue;
2596
+ bestSegment.set(key, segment);
2597
+ if (nodes.length >= nodeBudget) {
2598
+ return {
2599
+ kind: "exhausted",
2600
+ reason: `search budget exhausted (${nodeBudget} nodes) before reaching a violating state`,
2601
+ nodesExplored: nodes.length
2602
+ };
1888
2603
  }
1889
- } else {
1890
- return null;
2604
+ nodes.push({ state: succ.state, step: succ.step, parent: idx, segment });
2605
+ const childIdx = nodes.length - 1;
2606
+ if (satisfiesBadIndexed(index, succ.state, property, sinkPlaces)) {
2607
+ const chain = reconstruct(nodes, childIdx);
2608
+ return { kind: "confirmed", ...chain, nodesExplored: nodes.length };
2609
+ }
2610
+ queue.push(childIdx);
1891
2611
  }
1892
2612
  }
1893
- return builder.build();
2613
+ if (truncated) {
2614
+ return {
2615
+ kind: "exhausted",
2616
+ reason: `no violating state within ${segmentBudget} abstract step(s) of a decoded state (${bestSegment.size} state(s) explored)`,
2617
+ nodesExplored: nodes.length
2618
+ };
2619
+ }
2620
+ return { kind: "no-chain", nodesExplored: nodes.length };
2621
+ }
2622
+ function reconstruct(nodes, last) {
2623
+ const states = [];
2624
+ const steps = [];
2625
+ for (let i = last; i >= 0; i = nodes[i].parent) {
2626
+ const node = nodes[i];
2627
+ states.push(node.state);
2628
+ if (node.step != null) steps.push(node.step);
2629
+ }
2630
+ states.reverse();
2631
+ steps.reverse();
2632
+ return { states, steps };
1894
2633
  }
1895
2634
 
1896
2635
  // src/verification/z3/name-coloured-encoder.ts
@@ -1899,25 +2638,29 @@ function colourSlotBound(coloured, semiflows) {
1899
2638
  const isSemiflow = (inv) => inv.weights.every((x) => x >= 0);
1900
2639
  let single = null;
1901
2640
  for (const inv of semiflows) {
1902
- if (isSemiflow(inv) && inv.constant >= 1 && coloured.every((pid) => w(inv, pid) >= 1)) {
2641
+ if (isSemiflow(inv) && coloured.every((pid) => w(inv, pid) >= 1)) {
1903
2642
  if (single === null || inv.constant < single) single = inv.constant;
1904
2643
  }
1905
2644
  }
1906
2645
  if (single !== null) return single;
1907
- let sumConst = 0;
1908
2646
  const covered = new Array(coloured.length).fill(false);
1909
2647
  for (const inv of semiflows) {
1910
- if (!isSemiflow(inv)) continue;
1911
- let touches = false;
2648
+ if (!isSemiflow(inv) || inv.constant !== 0) continue;
1912
2649
  for (let i = 0; i < coloured.length; i++) {
1913
- if (w(inv, coloured[i]) >= 1) {
1914
- covered[i] = true;
1915
- touches = true;
1916
- }
2650
+ if (w(inv, coloured[i]) >= 1) covered[i] = true;
2651
+ }
2652
+ }
2653
+ const free = [...covered];
2654
+ let sumConst = 0;
2655
+ for (const inv of semiflows) {
2656
+ if (!isSemiflow(inv) || inv.constant === 0) continue;
2657
+ if (!coloured.some((pid, i) => !free[i] && w(inv, pid) >= 1)) continue;
2658
+ for (let i = 0; i < coloured.length; i++) {
2659
+ if (w(inv, coloured[i]) >= 1) covered[i] = true;
1917
2660
  }
1918
- if (touches) sumConst += inv.constant;
2661
+ sumConst += inv.constant;
1919
2662
  }
1920
- if (covered.every((c) => c) && sumConst >= 1) return sumConst;
2663
+ if (covered.every((c) => c)) return sumConst;
1921
2664
  return null;
1922
2665
  }
1923
2666
  function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrierPlaces, semiflows) {
@@ -1947,6 +2690,7 @@ function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrie
1947
2690
  }
1948
2691
  const k = colourSlotBound(coloured, semiflows);
1949
2692
  if (k === null) return null;
2693
+ if (k === 0 && coloured.length === P) return null;
1950
2694
  const budgetIdx = /* @__PURE__ */ new Set();
1951
2695
  for (const n of budgetNames) {
1952
2696
  const i = flat.placeIndex.get(n);
@@ -1982,324 +2726,278 @@ function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrie
1982
2726
  }
1983
2727
  return { coloured, isColoured, k, classes };
1984
2728
  }
1985
- function buildLayout(ctx, plan, P) {
2729
+ function buildLayout(plan, P) {
1986
2730
  const colUnc = new Array(P).fill(-1);
1987
2731
  const colCol = Array.from({ length: P }, () => []);
1988
- let nCols = 0;
2732
+ const cur = [];
2733
+ const nxt = [];
1989
2734
  for (let i = 0; i < P; i++) {
1990
2735
  if (plan.isColoured[i]) {
1991
2736
  const idxs = [];
1992
- for (let c = 0; c < plan.k; c++) idxs.push(nCols++);
2737
+ for (let c = 0; c < plan.k; c++) {
2738
+ idxs.push(cur.length);
2739
+ cur.push(`m${i}_${c}`);
2740
+ nxt.push(`m${i}_${c}p`);
2741
+ }
1993
2742
  colCol[i] = idxs;
1994
2743
  } else {
1995
- colUnc[i] = nCols++;
2744
+ colUnc[i] = cur.length;
2745
+ cur.push(`m${i}`);
2746
+ nxt.push(`m${i}p`);
1996
2747
  }
1997
2748
  }
1998
- const cur = [];
1999
- const nxt = [];
2000
- for (let col = 0; col < nCols; col++) {
2001
- cur.push(ctx.Int.const(`c${col}`));
2002
- nxt.push(ctx.Int.const(`cp${col}`));
2003
- }
2004
- return { colUnc, colCol, nCols, cur, nxt };
2749
+ return { colUnc, colCol, cur, nxt };
2005
2750
  }
2006
- function encodeColoured(ctx, fp, plan, flat, initial, property, invariants, sinkPlaces = /* @__PURE__ */ new Set()) {
2751
+ function quantified2(names) {
2752
+ return names.map((v) => `(${v} Int)`).join(" ");
2753
+ }
2754
+ function encodeColoured(plan, flat, initial, property, invariants, sinkPlaces) {
2007
2755
  const P = flat.places.length;
2008
2756
  const k = plan.k;
2009
- const lay = buildLayout(ctx, plan, P);
2010
- const intSort = ctx.Int.sort();
2011
- const boolSort = ctx.Bool.sort();
2012
- const markingSorts = new Array(lay.nCols).fill(intSort);
2013
- const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
2014
- fp.registerRelation(reachable);
2015
- const error = ctx.Function.declare("Error", boolSort);
2016
- fp.registerRelation(error);
2017
- const initArgs = new Array(lay.nCols);
2757
+ const lay = buildLayout(plan, P);
2758
+ const nCols = lay.cur.length;
2759
+ const lines = [];
2760
+ lines.push("(set-logic HORN)");
2761
+ lines.push("");
2762
+ lines.push(`(declare-fun Reachable (${new Array(nCols).fill("Int").join(" ")}) Bool)`);
2763
+ lines.push("(declare-fun Error () Bool)");
2764
+ lines.push("");
2765
+ const init = [];
2018
2766
  for (let i = 0; i < P; i++) {
2019
2767
  if (plan.isColoured[i]) {
2020
- for (let c = 0; c < k; c++) initArgs[lay.colCol[i][c]] = ctx.Int.val(0);
2768
+ for (let c = 0; c < k; c++) init.push("0");
2021
2769
  } else {
2022
- initArgs[lay.colUnc[i]] = ctx.Int.val(initial.tokens(flat.places[i]));
2770
+ init.push(String(initial.tokens(flat.places[i])));
2023
2771
  }
2024
2772
  }
2025
- fp.addRule(reachable.call(...initArgs), "init");
2773
+ lines.push(`(assert (Reachable ${init.join(" ")}))`);
2774
+ lines.push("");
2026
2775
  for (let ti = 0; ti < plan.classes.length; ti++) {
2027
2776
  const cls = plan.classes[ti];
2028
2777
  const ft = flat.transitions[ti];
2029
- if (cls.kind === "untouched") {
2030
- addRule(
2031
- ctx,
2032
- fp,
2033
- reachable,
2034
- lay,
2035
- plan,
2036
- invariants,
2037
- `${ft.name}_u`,
2038
- (enab, upd) => uncolouredIncidence(ctx, lay, plan, ft, enab, upd)
2039
- );
2040
- } else if (cls.kind === "mint") {
2041
- const colouredOut = cls.colouredOut;
2042
- for (let c = 0; c < k; c++) {
2043
- const cc = c;
2044
- addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_mint_${cc}`, (enab, upd) => {
2045
- uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
2046
- for (const q of plan.coloured) enab.push(lay.cur[lay.colCol[q][cc]].eq(0));
2047
- for (const o of colouredOut) {
2048
- const col = lay.colCol[o][cc];
2049
- upd.set(col, lay.cur[col].add(1));
2050
- }
2051
- });
2052
- }
2053
- } else if (cls.kind === "join") {
2054
- const colouredIn = cls.colouredIn;
2055
- for (let c = 0; c < k; c++) {
2056
- const cc = c;
2057
- addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_join_${cc}`, (enab, upd) => {
2058
- uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
2059
- for (const ip of colouredIn) {
2060
- const col = lay.colCol[ip][cc];
2061
- enab.push(lay.cur[col].ge(1));
2062
- upd.set(col, lay.cur[col].add(-1));
2063
- }
2064
- });
2065
- }
2066
- } else {
2067
- const inputCol = cls.inputCol;
2068
- const colouredOut = cls.colouredOut;
2069
- for (let c = 0; c < k; c++) {
2070
- const cc = c;
2071
- addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_consume_${cc}`, (enab, upd) => {
2072
- uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
2073
- const icol = lay.colCol[inputCol][cc];
2074
- enab.push(lay.cur[icol].ge(1));
2075
- upd.set(icol, lay.cur[icol].add(-1));
2076
- for (const o of colouredOut) {
2077
- const ocol = lay.colCol[o][cc];
2078
- upd.set(ocol, lay.cur[ocol].add(1));
2079
- }
2080
- });
2081
- }
2778
+ switch (cls.kind) {
2779
+ case "untouched":
2780
+ lines.push(encodeRule(plan, lay, invariants, (enab, upd) => uncolouredIncidence(lay, plan, ft, enab, upd)));
2781
+ break;
2782
+ case "mint":
2783
+ for (let c = 0; c < k; c++) {
2784
+ lines.push(encodeRule(plan, lay, invariants, (enab, upd) => {
2785
+ uncolouredIncidence(lay, plan, ft, enab, upd);
2786
+ for (const q of plan.coloured) enab.push(`(= ${lay.cur[lay.colCol[q][c]]} 0)`);
2787
+ for (const o of cls.colouredOut) {
2788
+ const col = lay.colCol[o][c];
2789
+ upd.push({ col, expr: `(+ ${lay.cur[col]} 1)` });
2790
+ }
2791
+ }));
2792
+ }
2793
+ break;
2794
+ case "join":
2795
+ for (let c = 0; c < k; c++) {
2796
+ lines.push(encodeRule(plan, lay, invariants, (enab, upd) => {
2797
+ uncolouredIncidence(lay, plan, ft, enab, upd);
2798
+ for (const ip of cls.colouredIn) {
2799
+ const col = lay.colCol[ip][c];
2800
+ enab.push(`(>= ${lay.cur[col]} 1)`);
2801
+ upd.push({ col, expr: `(- ${lay.cur[col]} 1)` });
2802
+ }
2803
+ }));
2804
+ }
2805
+ break;
2806
+ case "consume":
2807
+ for (let c = 0; c < k; c++) {
2808
+ lines.push(encodeRule(plan, lay, invariants, (enab, upd) => {
2809
+ uncolouredIncidence(lay, plan, ft, enab, upd);
2810
+ const icol = lay.colCol[cls.inputCol][c];
2811
+ enab.push(`(>= ${lay.cur[icol]} 1)`);
2812
+ upd.push({ col: icol, expr: `(- ${lay.cur[icol]} 1)` });
2813
+ for (const o of cls.colouredOut) {
2814
+ const ocol = lay.colCol[o][c];
2815
+ upd.push({ col: ocol, expr: `(+ ${lay.cur[ocol]} 1)` });
2816
+ }
2817
+ }));
2818
+ }
2819
+ break;
2082
2820
  }
2083
2821
  }
2084
- if (!addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property, sinkPlaces)) {
2085
- return null;
2086
- }
2087
- return {
2088
- errorExpr: error.call(),
2089
- reachableDecl: reachable
2090
- };
2822
+ lines.push("");
2823
+ const error = encodeError(plan, lay, flat, property, sinkPlaces, injectionMap(flat));
2824
+ if (error == null) return null;
2825
+ lines.push(error);
2826
+ lines.push("");
2827
+ lines.push("(assert (not Error))");
2828
+ lines.push("(check-sat)");
2829
+ return { smt2: lines.join("\n"), placeCount: P };
2091
2830
  }
2092
- function addRule(ctx, fp, reachable, lay, plan, invariants, ruleName, fill) {
2831
+ function encodeRule(plan, lay, invariants, fill) {
2093
2832
  const enab = [];
2094
- const upd = /* @__PURE__ */ new Map();
2833
+ const upd = [];
2095
2834
  fill(enab, upd);
2096
- const conds = [reachable.call(...lay.cur), ...enab];
2097
- for (let col = 0; col < lay.nCols; col++) {
2098
- const expr = upd.get(col);
2099
- if (expr !== void 0) {
2100
- conds.push(lay.nxt[col].eq(expr), lay.nxt[col].ge(0));
2835
+ const conditions = [`(Reachable ${lay.cur.join(" ")})`, ...enab];
2836
+ const changed = new Array(lay.cur.length).fill(null);
2837
+ for (const u of upd) changed[u.col] = u.expr;
2838
+ for (let col = 0; col < lay.cur.length; col++) {
2839
+ const expr = changed[col];
2840
+ if (expr != null) {
2841
+ conditions.push(`(= ${lay.nxt[col]} ${expr})`);
2842
+ conditions.push(`(>= ${lay.nxt[col]} 0)`);
2101
2843
  } else {
2102
- conds.push(lay.nxt[col].eq(lay.cur[col]));
2844
+ conditions.push(`(= ${lay.nxt[col]} ${lay.cur[col]})`);
2103
2845
  }
2104
2846
  }
2105
2847
  for (const inv of invariants) {
2106
- const eq = liftedInvariant(ctx, inv, plan, lay, lay.nxt);
2107
- if (eq) conds.push(eq);
2848
+ const eq = liftedInvariant(inv, plan, lay, lay.nxt);
2849
+ if (eq != null) conditions.push(eq);
2108
2850
  }
2109
- const body = ctx.And(...conds);
2110
- const head = reachable.call(...lay.nxt);
2111
- const qRule = ctx.ForAll([...lay.cur, ...lay.nxt], ctx.Implies(body, head));
2112
- fp.addRule(qRule, ruleName);
2851
+ const body = `(and ${conditions.join("\n ")})`;
2852
+ return `(assert (forall (${quantified2([...lay.cur, ...lay.nxt])})
2853
+ (=> ${body}
2854
+ (Reachable ${lay.nxt.join(" ")}))))`;
2113
2855
  }
2114
- function uncolouredIncidence(ctx, lay, plan, ft, enab, upd) {
2856
+ function uncolouredIncidence(lay, plan, ft, enab, upd) {
2115
2857
  const P = ft.preVector.length;
2116
2858
  for (let i = 0; i < P; i++) {
2117
2859
  if (plan.isColoured[i]) continue;
2118
2860
  const col = lay.colUnc[i];
2119
2861
  const pre = ft.preVector[i];
2120
- if (pre > 0) enab.push(lay.cur[col].ge(pre));
2862
+ if (pre > 0) enab.push(`(>= ${lay.cur[col]} ${pre})`);
2121
2863
  if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
2122
- upd.set(col, ctx.Int.val(ft.postVector[i]));
2864
+ upd.push({ col, expr: String(ft.postVector[i]) });
2123
2865
  } else {
2124
2866
  const delta = ft.postVector[i] - ft.preVector[i];
2125
- if (delta !== 0) upd.set(col, lay.cur[col].add(delta));
2867
+ if (delta > 0) upd.push({ col, expr: `(+ ${lay.cur[col]} ${delta})` });
2868
+ else if (delta < 0) upd.push({ col, expr: `(- ${lay.cur[col]} ${-delta})` });
2126
2869
  }
2127
2870
  }
2128
- for (const pid of ft.inhibitorPlaces) enab.push(lay.cur[lay.colUnc[pid]].eq(0));
2129
- for (const pid of ft.readPlaces) enab.push(lay.cur[lay.colUnc[pid]].ge(1));
2871
+ for (const pid of ft.inhibitorPlaces) enab.push(`(= ${lay.cur[lay.colUnc[pid]]} 0)`);
2872
+ for (const pid of ft.readPlaces) enab.push(`(>= ${lay.cur[lay.colUnc[pid]]} 1)`);
2130
2873
  }
2131
- function aggregate(plan, lay, place, vars) {
2874
+ function aggregate(plan, lay, place, names) {
2132
2875
  if (plan.isColoured[place]) {
2133
2876
  const cols = lay.colCol[place];
2134
- let sum = vars[cols[0]];
2135
- for (let c = 1; c < cols.length; c++) sum = sum.add(vars[cols[c]]);
2136
- return sum;
2877
+ if (cols.length === 0) return "0";
2878
+ if (cols.length === 1) return names[cols[0]];
2879
+ return `(+ ${cols.map((c) => names[c]).join(" ")})`;
2137
2880
  }
2138
- return vars[lay.colUnc[place]];
2881
+ return names[lay.colUnc[place]];
2139
2882
  }
2140
- function liftedInvariant(ctx, inv, plan, lay, vars) {
2141
- if (inv.support.size === 0) return null;
2142
- let sum = ctx.Int.val(0);
2143
- for (const i of inv.support) {
2144
- const agg = aggregate(plan, lay, i, vars);
2883
+ function liftedInvariant(inv, plan, lay, names) {
2884
+ const terms = [];
2885
+ for (const i of [...inv.support].sort((a, b) => a - b)) {
2886
+ const agg = aggregate(plan, lay, i, names);
2145
2887
  const w = inv.weights[i];
2146
- sum = sum.add(w === 1 ? agg : agg.mul(w));
2147
- }
2148
- return sum.eq(inv.constant);
2149
- }
2150
- function addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property, sinkPlaces) {
2151
- const violation = encodeViolation(ctx, plan, lay, flat, property, lay.cur, sinkPlaces);
2152
- if (violation === null) return false;
2153
- const reachBody = reachable.call(...lay.cur);
2154
- const body = ctx.And(reachBody, violation);
2155
- const head = error.call();
2156
- const qRule = ctx.ForAll([...lay.cur], ctx.Implies(body, head));
2157
- fp.addRule(qRule, "error");
2158
- return true;
2888
+ terms.push(w === 1 ? agg : `(* ${w} ${agg})`);
2889
+ }
2890
+ if (terms.length === 0) return null;
2891
+ const sum = terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
2892
+ return `(= ${sum} ${inv.constant})`;
2159
2893
  }
2160
- function encodeViolation(ctx, plan, lay, flat, property, cur, sinkPlaces) {
2894
+ function encodeError(plan, lay, flat, property, sinkPlaces, envInj) {
2895
+ const violation = encodeViolation(plan, lay, flat, property, sinkPlaces, envInj);
2896
+ if (violation == null) return null;
2897
+ return `(assert (forall (${quantified2(lay.cur)})
2898
+ (=> (and (Reachable ${lay.cur.join(" ")}) ${violation})
2899
+ Error)))`;
2900
+ }
2901
+ function encodeViolation(plan, lay, flat, property, sinkPlaces, envInj) {
2902
+ const anyPlacePresent = (places) => {
2903
+ const conds = indexOrdered(flat, places).map((pid) => `(>= ${aggregate(plan, lay, pid, lay.cur)} 1)`);
2904
+ return conds.length === 0 ? "false" : `(and ${conds.join(" ")})`;
2905
+ };
2161
2906
  switch (property.type) {
2162
2907
  case "place-bound":
2163
2908
  case "branch-place-bound": {
2164
- const idx = flatNetIndexOf(flat, property.place);
2165
- if (idx < 0) return null;
2166
- return aggregate(plan, lay, idx, cur).gt(property.bound);
2167
- }
2168
- case "mutual-exclusion": {
2169
- const i1 = flatNetIndexOf(flat, property.p1);
2170
- const i2 = flatNetIndexOf(flat, property.p2);
2171
- if (i1 < 0 || i2 < 0) return ctx.Bool.val(false);
2172
- return ctx.And(aggregate(plan, lay, i1, cur).ge(1), aggregate(plan, lay, i2, cur).ge(1));
2173
- }
2174
- case "unreachable": {
2175
- const conds = [];
2176
- for (const place of property.places) {
2177
- const idx = flatNetIndexOf(flat, place);
2178
- if (idx >= 0) conds.push(aggregate(plan, lay, idx, cur).ge(1));
2179
- }
2180
- if (conds.length === 0) return ctx.Bool.val(false);
2181
- return conds.length === 1 ? conds[0] : ctx.And(...conds);
2909
+ const pid = flat.placeIndex.get(property.place.name);
2910
+ if (pid == null) return null;
2911
+ return `(> ${aggregate(plan, lay, pid, lay.cur)} ${property.bound})`;
2182
2912
  }
2913
+ case "mutual-exclusion":
2914
+ return anyPlacePresent([property.p1, property.p2]);
2915
+ case "unreachable":
2916
+ return anyPlacePresent(property.places);
2183
2917
  case "deadlock-free":
2184
- return encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces);
2918
+ return encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj);
2185
2919
  case "joined-or-dead-lettered": {
2186
- const idx = flatNetIndexOf(flat, property.pending);
2187
- if (idx < 0) return null;
2188
- const deadlock = encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces);
2189
- return ctx.And(deadlock, aggregate(plan, lay, idx, cur).ge(1));
2920
+ const pid = flat.placeIndex.get(property.pending.name);
2921
+ if (pid == null) return null;
2922
+ const deadlock = encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj);
2923
+ return `(and ${deadlock} (>= ${aggregate(plan, lay, pid, lay.cur)} 1))`;
2190
2924
  }
2191
2925
  }
2192
2926
  }
2193
- function andAll(ctx, xs) {
2194
- if (xs.length === 0) return ctx.Bool.val(true);
2195
- let r = xs[0];
2196
- for (let i = 1; i < xs.length; i++) r = ctx.And(r, xs[i]);
2197
- return r;
2198
- }
2199
- function orAll(ctx, xs) {
2200
- if (xs.length === 0) return ctx.Bool.val(false);
2201
- let r = xs[0];
2202
- for (let i = 1; i < xs.length; i++) r = ctx.Or(r, xs[i]);
2203
- return r;
2204
- }
2205
- function injectedEnvIndices2(flat) {
2206
- const out = /* @__PURE__ */ new Map();
2207
- for (const [name, bound] of flat.environmentInjection) {
2208
- const idx = flat.placeIndex.get(name);
2209
- if (idx != null) out.set(idx, bound);
2210
- }
2211
- return out;
2212
- }
2213
- function uncolouredDisable(ft, lay, plan, envInj) {
2214
- const reasons = [];
2927
+ function uncolouredDisable(ft, lay, plan, envInj, reasons) {
2215
2928
  let permanentlyDisabled = false;
2216
2929
  const P = ft.preVector.length;
2217
2930
  for (let i = 0; i < P; i++) {
2218
2931
  if (plan.isColoured[i] || ft.preVector[i] === 0) continue;
2219
2932
  if (envInj.has(i)) {
2220
2933
  const bound = envInj.get(i);
2221
- if (bound !== null && ft.preVector[i] > bound) permanentlyDisabled = true;
2934
+ if (bound != null && ft.preVector[i] > bound) permanentlyDisabled = true;
2222
2935
  continue;
2223
2936
  }
2224
- reasons.push(lay.cur[lay.colUnc[i]].lt(ft.preVector[i]));
2225
- }
2226
- for (const inh of ft.inhibitorPlaces) {
2227
- reasons.push(lay.cur[lay.colUnc[inh]].gt(0));
2937
+ reasons.push(`(< ${lay.cur[lay.colUnc[i]]} ${ft.preVector[i]})`);
2228
2938
  }
2939
+ for (const inh of ft.inhibitorPlaces) reasons.push(`(> ${lay.cur[lay.colUnc[inh]]} 0)`);
2229
2940
  for (const rd of ft.readPlaces) {
2230
2941
  if (envInj.has(rd)) {
2231
2942
  const bound = envInj.get(rd);
2232
- if (bound !== null && bound < 1) permanentlyDisabled = true;
2943
+ if (bound != null && bound < 1) permanentlyDisabled = true;
2233
2944
  continue;
2234
2945
  }
2235
- reasons.push(lay.cur[lay.colUnc[rd]].lt(1));
2946
+ reasons.push(`(< ${lay.cur[lay.colUnc[rd]]} 1)`);
2236
2947
  }
2237
- return { reasons, permanentlyDisabled };
2948
+ return permanentlyDisabled;
2238
2949
  }
2239
- function colouredDisabledTerm(ctx, cls, plan, lay) {
2950
+ function colouredDisabledTerm(cls, plan, lay) {
2240
2951
  const k = plan.k;
2952
+ if (k === 0) {
2953
+ return cls.kind === "untouched" ? null : "true";
2954
+ }
2241
2955
  switch (cls.kind) {
2242
2956
  case "untouched":
2243
2957
  return null;
2244
2958
  case "mint": {
2245
2959
  const perColour = [];
2246
2960
  for (let c = 0; c < k; c++) {
2247
- const present = plan.coloured.map((q) => lay.cur[lay.colCol[q][c]].ge(1));
2248
- perColour.push(orAll(ctx, present));
2961
+ const present = plan.coloured.map((q) => `(>= ${lay.cur[lay.colCol[q][c]]} 1)`);
2962
+ perColour.push(`(or ${present.join(" ")})`);
2249
2963
  }
2250
- return andAll(ctx, perColour);
2964
+ return `(and ${perColour.join(" ")})`;
2251
2965
  }
2252
2966
  case "join": {
2253
2967
  const perColour = [];
2254
2968
  for (let c = 0; c < k; c++) {
2255
- const missing = cls.colouredIn.map((i) => lay.cur[lay.colCol[i][c]].eq(0));
2256
- perColour.push(orAll(ctx, missing));
2969
+ const missing = cls.colouredIn.map((i) => `(= ${lay.cur[lay.colCol[i][c]]} 0)`);
2970
+ perColour.push(`(or ${missing.join(" ")})`);
2257
2971
  }
2258
- return andAll(ctx, perColour);
2972
+ return `(and ${perColour.join(" ")})`;
2259
2973
  }
2260
2974
  case "consume": {
2261
2975
  const perColour = [];
2262
- for (let c = 0; c < k; c++) {
2263
- perColour.push(lay.cur[lay.colCol[cls.inputCol][c]].eq(0));
2264
- }
2265
- return andAll(ctx, perColour);
2976
+ for (let c = 0; c < k; c++) perColour.push(`(= ${lay.cur[lay.colCol[cls.inputCol][c]]} 0)`);
2977
+ return `(and ${perColour.join(" ")})`;
2266
2978
  }
2267
2979
  }
2268
2980
  }
2269
- function encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces) {
2270
- const envInj = injectedEnvIndices2(flat);
2981
+ function encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj) {
2271
2982
  const disabledConditions = [];
2272
2983
  for (let ti = 0; ti < plan.classes.length; ti++) {
2273
2984
  const cls = plan.classes[ti];
2274
2985
  const ft = flat.transitions[ti];
2275
- const { reasons, permanentlyDisabled } = uncolouredDisable(ft, lay, plan, envInj);
2986
+ const reasons = [];
2987
+ const permanentlyDisabled = uncolouredDisable(ft, lay, plan, envInj, reasons);
2276
2988
  if (permanentlyDisabled) {
2277
- disabledConditions.push(ctx.Bool.val(true));
2989
+ disabledConditions.push("true");
2278
2990
  continue;
2279
2991
  }
2280
- const term = colouredDisabledTerm(ctx, cls, plan, lay);
2281
- if (term !== null) reasons.push(term);
2282
- if (reasons.length === 0) {
2283
- return ctx.Bool.val(false);
2284
- }
2285
- disabledConditions.push(reasons.length === 1 ? reasons[0] : orAll(ctx, reasons));
2992
+ const term = colouredDisabledTerm(cls, plan, lay);
2993
+ if (term != null) reasons.push(term);
2994
+ if (reasons.length === 0) return "false";
2995
+ disabledConditions.push(reasons.length === 1 ? reasons[0] : `(or ${reasons.join(" ")})`);
2286
2996
  }
2287
- const sinkIndices = /* @__PURE__ */ new Set();
2288
- for (const sink of sinkPlaces) {
2289
- const idx = flatNetIndexOf(flat, sink);
2290
- if (idx >= 0) sinkIndices.add(idx);
2291
- }
2292
- if (sinkIndices.size > 0) {
2293
- const nonSink = [];
2294
- for (let pid = 0; pid < flat.places.length; pid++) {
2295
- if (sinkIndices.has(pid)) continue;
2296
- nonSink.push(aggregate(plan, lay, pid, lay.cur).ge(1));
2297
- }
2298
- if (nonSink.length > 0) {
2299
- disabledConditions.push(orAll(ctx, nonSink));
2300
- }
2997
+ for (const pid of indexOrdered(flat, sinkPlaces)) {
2998
+ disabledConditions.push(`(= ${aggregate(plan, lay, pid, lay.cur)} 0)`);
2301
2999
  }
2302
- return andAll(ctx, disabledConditions);
3000
+ return disabledConditions.length === 0 ? "true" : `(and ${disabledConditions.join(" ")})`;
2303
3001
  }
2304
3002
 
2305
3003
  // src/verification/analysis/name-fragment.ts
@@ -2366,9 +3064,9 @@ function classify(net, mode, carrierPlaces) {
2366
3064
  role: (tn) => roles.get(tn) ?? { type: "ordinary" }
2367
3065
  };
2368
3066
  }
2369
- function fixedRequiredCount(t, placeName) {
3067
+ function fixedRequiredCount(t, placeName2) {
2370
3068
  for (const spec of t.inputSpecs) {
2371
- if (spec.place.name === placeName) {
3069
+ if (spec.place.name === placeName2) {
2372
3070
  switch (spec.type) {
2373
3071
  case "one":
2374
3072
  return 1;
@@ -2478,13 +3176,21 @@ function compareNumberArrays(a, b) {
2478
3176
  var NameStateClass = class {
2479
3177
  base;
2480
3178
  names;
2481
- key;
2482
- constructor(base, names, colouredOrder) {
3179
+ /** The symmetry-canonical name-partition key (the name layer's intern key). */
3180
+ nameKey;
3181
+ constructor(base, names, colouredOrder, nameKey) {
2483
3182
  this.base = base;
2484
3183
  this.names = names;
2485
- this.key = `${base.marking.toString()}|${base.firingDomain.toString()}||${names.canonicalKey(colouredOrder)}`;
3184
+ this.nameKey = nameKey ?? names.canonicalKey(colouredOrder);
3185
+ }
3186
+ /** Full dedup key: the base key (marking + DBM zone) joined with the name key. */
3187
+ get key() {
3188
+ return `${baseKeyOf(this.base)}||${this.nameKey}`;
2486
3189
  }
2487
3190
  };
3191
+ function baseKeyOf(base) {
3192
+ return `${base.marking.toString()}|${base.firingDomain.toString()}`;
3193
+ }
2488
3194
 
2489
3195
  // src/verification/analysis/name-state-class-graph.ts
2490
3196
  var NameStateClassGraph = class _NameStateClassGraph {
@@ -2513,9 +3219,16 @@ var NameStateClassGraph = class _NameStateClassGraph {
2513
3219
  }
2514
3220
  const graph = new _NameStateClassGraph();
2515
3221
  const base0 = initialStateClass(net, initialMarking, envPlaces, envMode);
2516
- const initial = new NameStateClass(base0, new NameMarking(), fragment.colouredOrder);
3222
+ const baseIntern = /* @__PURE__ */ new Map();
3223
+ const nameIntern = /* @__PURE__ */ new Map();
2517
3224
  const indexOf = /* @__PURE__ */ new Map();
2518
- graph.pushClass(initial, indexOf);
3225
+ const b0 = internBase(baseIntern, base0);
3226
+ const n0 = internNames(nameIntern, new NameMarking(), fragment.colouredOrder);
3227
+ graph.pushClass(
3228
+ new NameStateClass(b0.base, n0.names, fragment.colouredOrder, n0.nameKey),
3229
+ classId(b0.id, n0.id),
3230
+ indexOf
3231
+ );
2519
3232
  const sym = { next: 0 };
2520
3233
  const queue = [0];
2521
3234
  while (queue.length > 0) {
@@ -2544,12 +3257,18 @@ var NameStateClassGraph = class _NameStateClassGraph {
2544
3257
  const baseSucc = computeSuccessor(net, current.base, vt, envPlaces, envMode);
2545
3258
  if (baseSucc === null || baseSucc.isEmpty()) continue;
2546
3259
  const nameSuccs = nameSuccessors(role, current.names, vt.outputPlaces, fragment, sym);
3260
+ const shared = internBase(baseIntern, baseSucc);
2547
3261
  for (const nm of nameSuccs) {
2548
- const succ = new NameStateClass(baseSucc, nm, fragment.colouredOrder);
2549
- let toIdx = indexOf.get(succ.key);
3262
+ const sharedNames = internNames(nameIntern, nm, fragment.colouredOrder);
3263
+ const id = classId(shared.id, sharedNames.id);
3264
+ let toIdx = indexOf.get(id);
2550
3265
  if (toIdx === void 0) {
2551
3266
  toIdx = graph.classes.length;
2552
- graph.pushClass(succ, indexOf);
3267
+ graph.pushClass(
3268
+ new NameStateClass(shared.base, sharedNames.names, fragment.colouredOrder, sharedNames.nameKey),
3269
+ id,
3270
+ indexOf
3271
+ );
2553
3272
  queue.push(toIdx);
2554
3273
  }
2555
3274
  graph.addEdge(curIdx, toIdx, transition.name);
@@ -2559,17 +3278,38 @@ var NameStateClassGraph = class _NameStateClassGraph {
2559
3278
  }
2560
3279
  return graph;
2561
3280
  }
2562
- pushClass(c, indexOf) {
3281
+ pushClass(c, id, indexOf) {
2563
3282
  const idx = this.classes.length;
2564
3283
  this.classes.push(c);
2565
3284
  this._successors.push([]);
2566
- indexOf.set(c.key, idx);
3285
+ indexOf.set(id, idx);
2567
3286
  }
2568
3287
  addEdge(from, to, name) {
2569
3288
  this.edges.push({ from, to, transitionName: name });
2570
3289
  this._successors[from].push(to);
2571
3290
  }
2572
3291
  };
3292
+ function classId(baseId, nameId) {
3293
+ return `${baseId}:${nameId}`;
3294
+ }
3295
+ function internBase(intern, base) {
3296
+ const key = `${baseKeyOf(base)}#${base.readyEarliest.join(",")}`;
3297
+ let entry = intern.get(key);
3298
+ if (entry === void 0) {
3299
+ entry = { id: intern.size, base };
3300
+ intern.set(key, entry);
3301
+ }
3302
+ return entry;
3303
+ }
3304
+ function internNames(intern, names, colouredOrder) {
3305
+ const nameKey = names.canonicalKey(colouredOrder);
3306
+ let entry = intern.get(nameKey);
3307
+ if (entry === void 0) {
3308
+ entry = { id: intern.size, names, nameKey };
3309
+ intern.set(nameKey, entry);
3310
+ }
3311
+ return entry;
3312
+ }
2573
3313
  var READY_EPS = 1e-9;
2574
3314
  function priorityDominated(l, idxL, enabled, readyEarliest, marking, names, fragment) {
2575
3315
  return enabled.some(
@@ -2602,10 +3342,10 @@ function sharesConsumedInput(h, l, marking) {
2602
3342
  }
2603
3343
  return false;
2604
3344
  }
2605
- function consumedDemand(t, placeName) {
3345
+ function consumedDemand(t, placeName2) {
2606
3346
  let demand = 0;
2607
3347
  for (const spec of t.inputSpecs) {
2608
- if (spec.place.name === placeName) demand += inputRequiredCount2(spec);
3348
+ if (spec.place.name === placeName2) demand += inputRequiredCount2(spec);
2609
3349
  }
2610
3350
  return demand;
2611
3351
  }
@@ -2792,6 +3532,7 @@ var SmtVerifier = class _SmtVerifier {
2792
3532
  constructor(net) {
2793
3533
  this.net = net;
2794
3534
  }
3535
+ net;
2795
3536
  _initialMarking = MarkingState.empty();
2796
3537
  _property = deadlockFree();
2797
3538
  _environmentPlaces = /* @__PURE__ */ new Set();
@@ -2799,6 +3540,9 @@ var SmtVerifier = class _SmtVerifier {
2799
3540
  _budgetPlaces = /* @__PURE__ */ new Set();
2800
3541
  _environmentMode = alwaysAvailable();
2801
3542
  _timeoutMs = 6e4;
3543
+ _certificateCheck = true;
3544
+ _counterexampleReplay = true;
3545
+ _semiflowInvariants = false;
2802
3546
  _nuMaxClasses = 1e5;
2803
3547
  _fragmentMode = "base";
2804
3548
  _carrierPlaces = /* @__PURE__ */ new Set();
@@ -2852,6 +3596,58 @@ var SmtVerifier = class _SmtVerifier {
2852
3596
  this._timeoutMs = ms;
2853
3597
  return this;
2854
3598
  }
3599
+ /**
3600
+ * Enables/disables the independent IC3 certificate check (default: enabled).
3601
+ *
3602
+ * When a proven verdict comes from the IC3/Spacer path on the flat count
3603
+ * encoding, the synthesized inductive invariant is re-validated with a plain
3604
+ * solver against the UNSTRENGTHENED step relation — VC1 (init), VC2
3605
+ * (consecution), VC3 (safety) — so a Spacer or encoder defect cannot certify
3606
+ * a false PROVEN. A certificate that fails validation downgrades the verdict
3607
+ * to unknown. Structural proofs and the coloured ν-encoding are unaffected.
3608
+ */
3609
+ certificateCheck(enabled) {
3610
+ this._certificateCheck = enabled;
3611
+ return this;
3612
+ }
3613
+ /**
3614
+ * Enables/disables abstract counterexample replay (default: enabled).
3615
+ *
3616
+ * When a violated verdict comes from the flat count encoding, the decoded
3617
+ * counterexample states (an order-free set — the derivation tree is walked in
3618
+ * traversal order, not firing order) are re-executed TS-side against the
3619
+ * abstract semantics the encoder emits (Lean's `fireA`, Basic.lean), searching
3620
+ * for a firing order from M₀ to a property-violating marking. See
3621
+ * `SmtVerificationResult.counterexampleConfirmed` for how each outcome lands.
3622
+ */
3623
+ counterexampleReplay(enabled) {
3624
+ this._counterexampleReplay = enabled;
3625
+ return this;
3626
+ }
3627
+ /**
3628
+ * Also hands the validated **P-semiflows** to the encoders as invariants
3629
+ * (VER-007; default: disabled — the encoders then see only the null-space basis).
3630
+ *
3631
+ * Every validated semiflow is a conservation law in its own right (`y >= 0`,
3632
+ * `y·C = 0`, `y·M0` exact, zero weight on every reset / consume-all place), and
3633
+ * the Farkas enumeration returns the *minimal* laws of the net. The null-space
3634
+ * basis the encoders get by default is one basis of many: elimination hands back
3635
+ * mixed-sign rows (discarded as not semi-positive) or rows that fold a reset place
3636
+ * into a chain whose other combinations avoid it (dropped by the H1 guard). On a
3637
+ * net with a few reset arcs that can lose every law of the chains those arcs
3638
+ * touch, and without them IC3 has to rediscover the conservation of each chain —
3639
+ * on a ~100-place net it does not within any practical budget. With the semiflows
3640
+ * in, the same reachability-safety queries close in about a second.
3641
+ *
3642
+ * Soundness is unchanged: the semiflows pass the same exact re-validation as the
3643
+ * basis rows, the union is pure strengthening (`Semiflow.lean`,
3644
+ * `semiflow_union_sound`), and the certificate check re-proves the strengthened
3645
+ * invariant. Off by default so reports stay byte-equal.
3646
+ */
3647
+ semiflowInvariants(enabled) {
3648
+ this._semiflowInvariants = enabled;
3649
+ return this;
3650
+ }
2855
3651
  /**
2856
3652
  * Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
2857
3653
  * Route B). When the symbolic name-aware graph would exceed this, the analysis
@@ -2906,6 +3702,62 @@ var SmtVerifier = class _SmtVerifier {
2906
3702
  this._prioritySemantics = semantics;
2907
3703
  return this;
2908
3704
  }
3705
+ /**
3706
+ * The SMT-LIB2 scripts {@link verify} would send to z3 for this configuration,
3707
+ * without running a solver (VER-013 AC1): the HORN query (flat, or name-coloured
3708
+ * when a declared budget puts the net on Route A's exact encoding) and, for the
3709
+ * flat encoding, the certificate-check script built around
3710
+ * {@link placeholderCertificate}. This is what the cross-language golden tests diff
3711
+ * byte for byte. Route B, the structural pre-check and the unresolved-place
3712
+ * refusal are bypassed: it is what Route A encodes.
3713
+ */
3714
+ encodeScripts() {
3715
+ requireOutputProducingActions(this.net);
3716
+ const hasMatch = [...this.net.transitions].some((t) => t.matchSpec !== null);
3717
+ const nuBounded = this._budgetPlaces.size > 0;
3718
+ const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
3719
+ const matrix = IncidenceMatrix.from(flatNet);
3720
+ const { valid: basis } = validateInvariantsExact(
3721
+ matrix,
3722
+ computePInvariants(matrix, flatNet, this._initialMarking),
3723
+ flatNet,
3724
+ this._initialMarking
3725
+ );
3726
+ const { valid: semiflows } = validateInvariantsExact(
3727
+ matrix,
3728
+ computePSemiflows(matrix, flatNet, this._initialMarking),
3729
+ flatNet,
3730
+ this._initialMarking
3731
+ );
3732
+ let invariants = basis;
3733
+ if (this._semiflowInvariants) invariants = strengthenWithSemiflows(basis, semiflows).invariants;
3734
+ invariants = canonicalInvariantOrder(invariants);
3735
+ if (hasMatch && nuBounded) {
3736
+ const plan = buildColouredPlan(
3737
+ this.net,
3738
+ flatNet,
3739
+ this._initialMarking,
3740
+ this._budgetPlaces,
3741
+ this._fragmentMode,
3742
+ this._carrierPlaces,
3743
+ semiflows
3744
+ );
3745
+ if (plan != null) {
3746
+ const coloured = encodeColoured(plan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
3747
+ if (coloured != null) return { horn: coloured.smt2, certificate: null, coloured: true };
3748
+ }
3749
+ }
3750
+ const horn = encode(flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces, this._counterexampleReplay).smt2;
3751
+ const certificate = vcScript(
3752
+ placeholderCertificate(flatNet.places.length),
3753
+ flatNet,
3754
+ this._initialMarking,
3755
+ this._property,
3756
+ this._sinkPlaces,
3757
+ invariants
3758
+ );
3759
+ return { horn, certificate, coloured: false };
3760
+ }
2909
3761
  /**
2910
3762
  * Runs the verification pipeline.
2911
3763
  *
@@ -2998,6 +3850,7 @@ var SmtVerifier = class _SmtVerifier {
2998
3850
  report.push("=== RESULT ===\n");
2999
3851
  report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
3000
3852
  report.push(" All siphons contain initially marked traps.");
3853
+ report.push(" Certificate check: not applicable (structural proof)");
3001
3854
  return buildResult(
3002
3855
  { type: "proven", method: "structural", inductiveInvariant: null },
3003
3856
  report.join("\n"),
@@ -3011,14 +3864,43 @@ var SmtVerifier = class _SmtVerifier {
3011
3864
  }
3012
3865
  report.push("Phase 3: Computing P-invariants...");
3013
3866
  const matrix = IncidenceMatrix.from(flatNet);
3014
- const invariants = computePInvariants(matrix, flatNet, this._initialMarking);
3015
- const semiflows = computePSemiflows(matrix, flatNet, this._initialMarking);
3016
- report.push(` Found: ${invariants.length} P-invariant(s)`);
3867
+ const { valid: basisInvariants, dropped: droppedInvariants } = validateInvariantsExact(
3868
+ matrix,
3869
+ computePInvariants(matrix, flatNet, this._initialMarking),
3870
+ flatNet,
3871
+ this._initialMarking
3872
+ );
3873
+ const { valid: semiflows, dropped: droppedSemiflows } = validateInvariantsExact(
3874
+ matrix,
3875
+ computePSemiflows(matrix, flatNet, this._initialMarking),
3876
+ flatNet,
3877
+ this._initialMarking
3878
+ );
3879
+ report.push(` Found: ${basisInvariants.length} P-invariant(s)`);
3880
+ let invariants = basisInvariants;
3881
+ if (this._semiflowInvariants) {
3882
+ const { invariants: strengthened, added } = strengthenWithSemiflows(basisInvariants, semiflows);
3883
+ invariants = strengthened;
3884
+ report.push(` Semiflows encoded as invariants: ${added}`);
3885
+ }
3886
+ invariants = canonicalInvariantOrder(invariants);
3017
3887
  const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
3018
3888
  report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
3019
3889
  for (const inv of invariants) {
3020
3890
  report.push(` ${formatInvariant(inv, flatNet)}`);
3021
3891
  }
3892
+ for (const { invariant, reason } of droppedInvariants) {
3893
+ report.push(` Dropped invariant: ${formatInvariant(invariant, flatNet)} - ${reason}`);
3894
+ }
3895
+ if (droppedInvariants.length > 0) {
3896
+ report.push(` Dropped: ${droppedInvariants.length} invariant(s) failed the exact re-check`);
3897
+ }
3898
+ for (const { invariant, reason } of droppedSemiflows) {
3899
+ report.push(` Dropped semiflow: ${formatInvariant(invariant, flatNet)} - ${reason}`);
3900
+ }
3901
+ if (droppedSemiflows.length > 0) {
3902
+ report.push(` Dropped: ${droppedSemiflows.length} semiflow(s) failed the exact re-check`);
3903
+ }
3022
3904
  report.push("");
3023
3905
  report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
3024
3906
  const colouredPlan = hasMatch && nuBounded ? buildColouredPlan(
@@ -3030,173 +3912,208 @@ var SmtVerifier = class _SmtVerifier {
3030
3912
  this._carrierPlaces,
3031
3913
  semiflows
3032
3914
  ) : null;
3033
- let runner;
3915
+ const stats = {
3916
+ places: flatNet.places.length,
3917
+ transitions: flatNet.transitions.length,
3918
+ invariantsFound: invariants.length,
3919
+ structuralResult: structResultStr
3920
+ };
3921
+ let solver;
3034
3922
  try {
3035
- runner = await createSpacerRunner(this._timeoutMs);
3923
+ solver = resolveZ3();
3036
3924
  } catch (e) {
3037
- report.push(` ERROR: ${e.message ?? e}
3925
+ const reason = e instanceof Z3Unavailable ? e.message : String(e?.message ?? e);
3926
+ report.push(` Solver: z3 unavailable (${reason})`);
3927
+ report.push(` Status: UNKNOWN (${reason})
3038
3928
  `);
3039
3929
  report.push("=== RESULT ===\n");
3040
- report.push(`UNKNOWN: Z3 initialization error: ${e.message ?? e}`);
3041
- return buildResult(
3042
- { type: "unknown", reason: `Z3 init error: ${e.message ?? e}` },
3043
- report.join("\n"),
3044
- invariants,
3045
- [],
3046
- [],
3047
- [],
3048
- performance.now() - start,
3049
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3930
+ report.push(`UNKNOWN: Could not determine ${propDesc}`);
3931
+ report.push(` Reason: ${reason}`);
3932
+ return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
3933
+ }
3934
+ report.push(` Solver: z3 ${formatZ3Version(solver.version)}`);
3935
+ let encoding;
3936
+ if (colouredPlan != null) {
3937
+ report.push(
3938
+ ` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
3050
3939
  );
3940
+ const coloured = encodeColoured(colouredPlan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
3941
+ if (coloured == null) {
3942
+ const reason = "property names a place that does not resolve in the net; refusing to certify (the encoding would be vacuously proven)";
3943
+ report.push(" Status: UNKNOWN (unresolved property place)\n");
3944
+ report.push("=== RESULT ===\n");
3945
+ report.push(`UNKNOWN: ${reason}`);
3946
+ return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
3947
+ }
3948
+ encoding = coloured;
3949
+ } else {
3950
+ const unresolved = unresolvedPropertyPlace(flatNet, this._property);
3951
+ if (unresolved != null) {
3952
+ const reason = `property names a place that does not resolve in the net ('${unresolved}'); refusing to certify (the encoding would be vacuously proven)`;
3953
+ report.push(" Status: UNKNOWN (unresolved property place)\n");
3954
+ report.push("=== RESULT ===\n");
3955
+ report.push(`UNKNOWN: ${reason}`);
3956
+ return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
3957
+ }
3958
+ encoding = encode(flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces, this._counterexampleReplay);
3051
3959
  }
3052
- try {
3053
- let encoding;
3054
- if (colouredPlan != null) {
3055
- report.push(
3056
- ` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
3057
- );
3058
- encoding = encodeColoured(runner.ctx, runner.fp, colouredPlan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
3059
- if (encoding == null) {
3060
- const reason = "property names a place that does not resolve in the net; refusing to certify (the encoding would be vacuously proven)";
3061
- report.push(" Status: UNKNOWN (unresolved property place)\n");
3960
+ const queryResult = await runZ3Spacer(
3961
+ solver,
3962
+ this._timeoutMs,
3963
+ encoding.smt2,
3964
+ colouredPlan != null ? "horn-coloured" : "horn"
3965
+ );
3966
+ switch (queryResult.type) {
3967
+ case "proven": {
3968
+ if (this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
3969
+ const reason = "environment places present but not modeled (mode=ignore); a proof would be vacuous \u2014 use alwaysAvailable() or bounded(k) to model external injection";
3970
+ report.push(` Status: UNSAT, but vacuous under ignore mode
3971
+ `);
3062
3972
  report.push("=== RESULT ===\n");
3063
3973
  report.push(`UNKNOWN: ${reason}`);
3064
- return buildResult(
3065
- { type: "unknown", reason },
3066
- report.join("\n"),
3974
+ return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
3975
+ }
3976
+ report.push(" Status: UNSAT (property holds)");
3977
+ if (colouredPlan != null) {
3978
+ report.push(" Certificate check: not applicable (name-coloured encoding)");
3979
+ } else if (!this._certificateCheck) {
3980
+ report.push(" Certificate check: not applicable (disabled)");
3981
+ } else {
3982
+ const certificate = await checkCertificate(
3983
+ queryResult.invariantFormula,
3984
+ flatNet,
3985
+ this._initialMarking,
3986
+ this._property,
3067
3987
  invariants,
3068
- [],
3069
- [],
3070
- [],
3071
- performance.now() - start,
3072
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3988
+ this._sinkPlaces,
3989
+ solver,
3990
+ this._timeoutMs
3073
3991
  );
3074
- }
3075
- } else {
3076
- encoding = encode(runner.ctx, runner.fp, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
3077
- }
3078
- const queryResult = await runner.query(encoding.errorExpr, encoding.reachableDecl);
3079
- switch (queryResult.type) {
3080
- case "proven": {
3081
- if (this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
3082
- const reason = "environment places present but not modeled (mode=ignore); a proof would be vacuous \u2014 use alwaysAvailable() or bounded(k) to model external injection";
3083
- report.push(` Status: UNSAT, but vacuous under ignore mode
3084
- `);
3992
+ const reason = certificateDowngradeReason(certificate);
3993
+ if (reason != null) {
3994
+ report.push(" Certificate check: FAILED");
3995
+ if (certificate.type !== "passed" && certificate.invariant != null) {
3996
+ report.push(" Uncertified invariant:");
3997
+ for (const line of certificate.invariant.split("\n")) report.push(` ${line}`);
3998
+ }
3999
+ report.push("");
3085
4000
  report.push("=== RESULT ===\n");
3086
4001
  report.push(`UNKNOWN: ${reason}`);
4002
+ return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
4003
+ }
4004
+ report.push(" Certificate check: PASSED (init, consecution, safety)");
4005
+ }
4006
+ report.push("");
4007
+ const formula = queryResult.invariantFormula;
4008
+ const discoveredInvariants = formula != null ? [formula] : [];
4009
+ if (formula != null) {
4010
+ report.push("Phase 5: Inductive invariant (discovered by IC3)");
4011
+ report.push(" Spacer synthesized:");
4012
+ for (const line of formula.split("\n")) report.push(` ${line}`);
4013
+ report.push(" This formula is INDUCTIVE: preserved by all transitions.");
4014
+ report.push("");
4015
+ }
4016
+ report.push("=== RESULT ===\n");
4017
+ report.push(`PROVEN (IC3/PDR): ${propDesc}`);
4018
+ report.push(" Z3 Spacer proved no reachable state violates the property.");
4019
+ report.push(" NOTE: Verification ignores timing constraints.");
4020
+ report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
4021
+ return this.applyNuGuard(buildResult(
4022
+ { type: "proven", method: "IC3/PDR", inductiveInvariant: formula },
4023
+ report.join("\n"),
4024
+ invariants,
4025
+ discoveredInvariants,
4026
+ [],
4027
+ [],
4028
+ performance.now() - start,
4029
+ stats
4030
+ ), hasMatch, nuBounded, colouredPlan != null);
4031
+ }
4032
+ case "violated": {
4033
+ report.push(" Status: SAT (counterexample found)\n");
4034
+ const decoded = decode(queryResult.answer, flatNet);
4035
+ if (decoded.note != null) report.push(` Counterexample decoding: ${decoded.note}`);
4036
+ let confirmed = null;
4037
+ let trace = [...decoded.states];
4038
+ let transitions = [];
4039
+ let replayed = false;
4040
+ if (colouredPlan == null && this._counterexampleReplay) {
4041
+ const assessment = assessCounterexample(
4042
+ flatNet,
4043
+ this._initialMarking,
4044
+ decoded.states,
4045
+ this._property,
4046
+ this._sinkPlaces
4047
+ );
4048
+ if (assessment.kind === "confirmed") {
4049
+ confirmed = true;
4050
+ replayed = true;
4051
+ trace = assessment.trace;
4052
+ transitions = assessment.firings;
4053
+ report.push(" Counterexample replay: CONFIRMED (abstract chain M0 -> bad re-executed)");
4054
+ } else if (assessment.kind === "unconfirmed") {
4055
+ confirmed = false;
4056
+ report.push(` Counterexample replay: UNCONFIRMED (${assessment.note})`);
4057
+ report.push(" The verdict rests on Spacer's answer.");
4058
+ } else {
4059
+ report.push(" Counterexample replay: FAILED");
4060
+ report.push(` Decoded states (order-free set, ${decoded.states.size}):`);
4061
+ for (const m of decoded.states) report.push(` ${m}`);
4062
+ report.push(` Raw Z3 answer: ${truncate(queryResult.answer, 2e3)}`);
4063
+ report.push("");
4064
+ report.push("=== RESULT ===\n");
4065
+ report.push(`UNKNOWN: ${assessment.reason}`);
3087
4066
  return buildResult(
3088
- { type: "unknown", reason },
4067
+ { type: "unknown", reason: assessment.reason },
3089
4068
  report.join("\n"),
3090
4069
  invariants,
3091
4070
  [],
3092
4071
  [],
3093
4072
  [],
3094
4073
  performance.now() - start,
3095
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
4074
+ stats,
4075
+ false
3096
4076
  );
3097
4077
  }
3098
- report.push(" Status: UNSAT (property holds)\n");
3099
- const discoveredInvariants = [];
3100
- if (queryResult.invariantFormula != null) {
3101
- discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
3102
- }
3103
- for (const level of queryResult.levelInvariants) {
3104
- discoveredInvariants.push(substituteNames(level, flatNet));
3105
- }
3106
- if (discoveredInvariants.length > 0) {
3107
- report.push("Phase 5: Inductive invariant (discovered by IC3)");
3108
- report.push(` Spacer synthesized: ${discoveredInvariants[0]}`);
3109
- report.push(" This formula is INDUCTIVE: preserved by all transitions.");
3110
- if (discoveredInvariants.length > 1) {
3111
- report.push(" Per-level clauses:");
3112
- for (let i = 1; i < discoveredInvariants.length; i++) {
3113
- report.push(` ${discoveredInvariants[i]}`);
3114
- }
3115
- }
3116
- report.push("");
3117
- }
3118
- report.push("=== RESULT ===\n");
3119
- report.push(`PROVEN (IC3/PDR): ${propDesc}`);
3120
- report.push(" Z3 Spacer proved no reachable state violates the property.");
3121
- report.push(" NOTE: Verification ignores timing constraints.");
3122
- report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
3123
- return this.applyNuGuard(buildResult(
3124
- {
3125
- type: "proven",
3126
- method: "IC3/PDR",
3127
- inductiveInvariant: queryResult.invariantFormula != null ? substituteNames(queryResult.invariantFormula, flatNet) : null
3128
- },
3129
- report.join("\n"),
3130
- invariants,
3131
- discoveredInvariants,
3132
- [],
3133
- [],
3134
- performance.now() - start,
3135
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3136
- ), hasMatch, nuBounded, colouredPlan != null);
3137
- }
3138
- case "violated": {
3139
- report.push(" Status: SAT (counterexample found)\n");
3140
- const decoded = decode(runner.ctx, queryResult.answer, flatNet);
3141
- report.push("=== RESULT ===\n");
3142
- report.push(`VIOLATED: ${propDesc}`);
3143
- if (decoded.trace.length > 0) {
3144
- report.push(` Counterexample trace (${decoded.trace.length} states):`);
3145
- for (let i = 0; i < decoded.trace.length; i++) {
3146
- report.push(` ${i}: ${decoded.trace[i]}`);
3147
- }
3148
- }
3149
- if (decoded.transitions.length > 0) {
3150
- report.push(` Firing sequence: ${decoded.transitions.join(" -> ")}`);
3151
- }
3152
- report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
3153
- report.push(" It may be spurious if timing constraints prevent this sequence.");
3154
- return this.applyNuGuard(buildResult(
3155
- { type: "violated" },
3156
- report.join("\n"),
3157
- invariants,
3158
- [],
3159
- decoded.trace,
3160
- decoded.transitions,
3161
- performance.now() - start,
3162
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3163
- ), hasMatch, nuBounded, colouredPlan != null);
3164
4078
  }
3165
- case "unknown": {
3166
- report.push(` Status: UNKNOWN (${queryResult.reason})
3167
- `);
3168
- report.push("=== RESULT ===\n");
3169
- report.push(`UNKNOWN: Could not determine ${propDesc}`);
3170
- report.push(` Reason: ${queryResult.reason}`);
3171
- return buildResult(
3172
- { type: "unknown", reason: queryResult.reason },
3173
- report.join("\n"),
3174
- invariants,
3175
- [],
3176
- [],
3177
- [],
3178
- performance.now() - start,
3179
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3180
- );
4079
+ report.push("=== RESULT ===\n");
4080
+ report.push(`VIOLATED: ${propDesc}`);
4081
+ if (trace.length > 0) {
4082
+ report.push(` Counterexample trace (${replayed ? "replay order, " : "proof order, "}${trace.length} states):`);
4083
+ for (let i = 0; i < trace.length; i++) report.push(` ${i}: ${trace[i]}`);
3181
4084
  }
4085
+ if (transitions.length > 0) report.push(` Firing sequence: ${transitions.join(" -> ")}`);
4086
+ report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
4087
+ report.push(" It may be spurious if timing constraints prevent this sequence.");
4088
+ return this.applyNuGuard(buildResult(
4089
+ { type: "violated" },
4090
+ report.join("\n"),
4091
+ invariants,
4092
+ [],
4093
+ trace,
4094
+ transitions,
4095
+ performance.now() - start,
4096
+ stats,
4097
+ confirmed
4098
+ ), hasMatch, nuBounded, colouredPlan != null);
3182
4099
  }
3183
- } catch (e) {
3184
- report.push(` ERROR: ${e.message ?? e}
4100
+ case "unknown": {
4101
+ report.push(` Status: UNKNOWN (${queryResult.reason})
3185
4102
  `);
3186
- report.push("=== RESULT ===\n");
3187
- report.push(`UNKNOWN: Z3 solver error: ${e.message ?? e}`);
3188
- return buildResult(
3189
- { type: "unknown", reason: `Z3 error: ${e.message ?? e}` },
3190
- report.join("\n"),
3191
- invariants,
3192
- [],
3193
- [],
3194
- [],
3195
- performance.now() - start,
3196
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3197
- );
3198
- } finally {
3199
- runner.dispose();
4103
+ report.push("=== RESULT ===\n");
4104
+ report.push(`UNKNOWN: Could not determine ${propDesc}`);
4105
+ report.push(` Reason: ${queryResult.reason}`);
4106
+ return buildResult(
4107
+ { type: "unknown", reason: queryResult.reason },
4108
+ report.join("\n"),
4109
+ invariants,
4110
+ [],
4111
+ [],
4112
+ [],
4113
+ performance.now() - start,
4114
+ stats
4115
+ );
4116
+ }
3200
4117
  }
3201
4118
  }
3202
4119
  /**
@@ -3252,6 +4169,57 @@ function isReachabilitySafety(property) {
3252
4169
  return false;
3253
4170
  }
3254
4171
  }
4172
+ function assessCounterexample(flatNet, initialMarking, decodedStates, property, sinkPlaces) {
4173
+ if (decodedStates.size === 0) {
4174
+ return {
4175
+ kind: "unconfirmed",
4176
+ note: "no counterexample states could be decoded from the Spacer answer, so the abstract replay could not run"
4177
+ };
4178
+ }
4179
+ let outcome;
4180
+ try {
4181
+ outcome = replayCounterexample(
4182
+ flatNet,
4183
+ vectorize(initialMarking, flatNet),
4184
+ [...decodedStates].map((m) => vectorize(m, flatNet)),
4185
+ property,
4186
+ sinkPlaces
4187
+ );
4188
+ } catch (e) {
4189
+ outcome = { kind: "exhausted", reason: `replay threw: ${e?.message ?? e}`, nodesExplored: 0 };
4190
+ }
4191
+ switch (outcome.kind) {
4192
+ case "confirmed":
4193
+ return {
4194
+ kind: "confirmed",
4195
+ trace: outcome.states.map((s) => toMarkingState(s, flatNet)),
4196
+ firings: outcome.steps.map(stepName)
4197
+ };
4198
+ case "exhausted":
4199
+ return { kind: "unconfirmed", note: `abstract replay did not complete: ${outcome.reason}` };
4200
+ case "no-chain":
4201
+ return {
4202
+ kind: "downgraded",
4203
+ reason: "counterexample replay found no firing chain to the violation under the abstract semantics, so VIOLATED is withheld"
4204
+ };
4205
+ }
4206
+ }
4207
+ function certificateDowngradeReason(outcome) {
4208
+ switch (outcome.type) {
4209
+ case "passed":
4210
+ return null;
4211
+ case "failed":
4212
+ 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`;
4213
+ case "unavailable":
4214
+ return `certificate check could not run: ${outcome.reason}; PROVEN is withheld without an independently validated certificate`;
4215
+ }
4216
+ }
4217
+ function placeholderCertificate(placeCount) {
4218
+ const params = [];
4219
+ for (let i = 0; i < placeCount; i++) params.push(`(x!${i} Int)`);
4220
+ return `(define-fun Reachable (${params.join(" ")}) Bool
4221
+ true)`;
4222
+ }
3255
4223
  function downgradeToUnknown(result, reason) {
3256
4224
  return {
3257
4225
  ...result,
@@ -3261,14 +4229,34 @@ Downgraded to UNKNOWN: ${reason}
3261
4229
  `,
3262
4230
  discoveredInvariants: [],
3263
4231
  counterexampleTrace: [],
3264
- counterexampleTransitions: []
4232
+ counterexampleTransitions: [],
4233
+ counterexampleConfirmed: null
3265
4234
  };
3266
4235
  }
3267
- function substituteNames(formula, flatNet) {
3268
- for (let i = flatNet.places.length - 1; i >= 0; i--) {
3269
- formula = formula.replace(new RegExp(`\\bm${i}\\b`, "g"), flatNet.places[i].name);
4236
+ function truncate(s, max) {
4237
+ return s.length <= max ? s : `${s.slice(0, max)}\u2026 (${s.length - max} chars truncated)`;
4238
+ }
4239
+ function unresolvedPropertyPlace(flatNet, property) {
4240
+ const named = (() => {
4241
+ switch (property.type) {
4242
+ case "deadlock-free":
4243
+ return [];
4244
+ case "mutual-exclusion":
4245
+ return [property.p1, property.p2];
4246
+ case "place-bound":
4247
+ return [property.place];
4248
+ case "branch-place-bound":
4249
+ return [property.place];
4250
+ case "unreachable":
4251
+ return [...property.places];
4252
+ case "joined-or-dead-lettered":
4253
+ return [property.pending];
4254
+ }
4255
+ })();
4256
+ for (const place of named) {
4257
+ if (!flatNet.placeIndex.has(place.name)) return place.name;
3270
4258
  }
3271
- return formula;
4259
+ return null;
3272
4260
  }
3273
4261
  function formatInvariant(inv, flatNet) {
3274
4262
  const parts = [];
@@ -3279,10 +4267,10 @@ function formatInvariant(inv, flatNet) {
3279
4267
  parts.push(flatNet.places[idx].name);
3280
4268
  }
3281
4269
  }
3282
- return `${parts.join(" + ")} = ${inv.constant}`;
4270
+ return `${parts.length === 0 ? "0" : parts.join(" + ")} = ${inv.constant}`;
3283
4271
  }
3284
- function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
3285
- return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
4272
+ function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics, counterexampleConfirmed = null) {
4273
+ return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, counterexampleConfirmed, elapsedMs, statistics };
3286
4274
  }
3287
4275
 
3288
4276
  // src/verification/smt-verification-result.ts
@@ -3336,23 +4324,42 @@ export {
3336
4324
  pInvariant,
3337
4325
  pInvariantToString,
3338
4326
  computePInvariants,
4327
+ strengthenWithSemiflows,
3339
4328
  computePSemiflows,
3340
4329
  isCoveredByInvariants,
4330
+ canonicalInvariantOrder,
3341
4331
  structuralCheck,
3342
4332
  findMinimalSiphons,
3343
4333
  findMaximalTrapIn,
3344
- createSpacerRunner,
3345
- flatNetPlaceCount,
3346
- flatNetTransitionCount,
3347
- flatNetIndexOf,
4334
+ Z3_ENV,
4335
+ DUMP_ENV,
4336
+ MIN_Z3_VERSION,
4337
+ parseZ3Version,
4338
+ formatZ3Version,
4339
+ Z3Unavailable,
4340
+ Z3ProcessError,
4341
+ z3SolverAt,
4342
+ resolveZ3,
4343
+ z3Available,
4344
+ runZ3Text,
4345
+ runZ3Spacer,
3348
4346
  encode,
4347
+ encodeStepRelationSmt2,
4348
+ checkCertificate,
4349
+ vcScript,
3349
4350
  DBM,
3350
4351
  StateClass,
3351
4352
  requireOutputProducingActions,
3352
4353
  StateClassGraph,
3353
4354
  decode,
4355
+ decodeStateSet,
4356
+ flatNetPlaceCount,
4357
+ flatNetTransitionCount,
4358
+ flatNetIndexOf,
4359
+ replayCounterexample,
3354
4360
  SmtVerifier,
4361
+ placeholderCertificate,
3355
4362
  isProven,
3356
4363
  isViolated
3357
4364
  };
3358
- //# sourceMappingURL=chunk-5W6SVYPD.js.map
4365
+ //# sourceMappingURL=chunk-WYCGGQAW.js.map