@fairfox/polly 0.83.0 → 0.85.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.
@@ -258,12 +258,35 @@ __export(exports_model_coverage, {
258
258
  function norm(field) {
259
259
  return field.replace(/_/g, ".");
260
260
  }
261
- function computeModelCoverage(stateFields, handlers) {
261
+ function foldOffSurface(offSurface, rawByKey) {
262
+ const offSurfaceByField = new Map;
263
+ const offSurfaceMutations = [];
264
+ for (const m of offSurface) {
265
+ const key = norm(m.field);
266
+ const raw = rawByKey.get(key);
267
+ if (raw === undefined)
268
+ continue;
269
+ offSurfaceMutations.push({
270
+ field: raw,
271
+ signalVariable: m.signalVariable,
272
+ function: m.functionName,
273
+ file: m.filePath,
274
+ line: m.line,
275
+ declared: true
276
+ });
277
+ const list = offSurfaceByField.get(key) ?? [];
278
+ list.push({ function: m.functionName, file: m.filePath, line: m.line });
279
+ offSurfaceByField.set(key, list);
280
+ }
281
+ return { offSurfaceByField, offSurfaceMutations };
282
+ }
283
+ function computeModelCoverage(stateFields, handlers, offSurface = []) {
262
284
  const writersByField = new Map;
263
285
  for (const field of stateFields) {
264
286
  writersByField.set(norm(field), new Set);
265
287
  }
266
288
  const declaredOrder = stateFields.map((f) => ({ raw: f, key: norm(f) }));
289
+ const rawByKey = new Map(declaredOrder.map((d) => [d.key, d.raw]));
267
290
  for (const handler of handlers) {
268
291
  for (const assignment of handler.assignments) {
269
292
  const key = norm(assignment.field);
@@ -272,10 +295,15 @@ function computeModelCoverage(stateFields, handlers) {
272
295
  writers.add(handler.messageType);
273
296
  }
274
297
  }
275
- const fieldCoverage = declaredOrder.map(({ raw, key }) => ({
276
- field: raw,
277
- writers: Array.from(writersByField.get(key) ?? []).sort()
278
- }));
298
+ const { offSurfaceByField, offSurfaceMutations } = foldOffSurface(offSurface, rawByKey);
299
+ const fieldCoverage = declaredOrder.map(({ raw, key }) => {
300
+ const offSurfaceWriters = offSurfaceByField.get(key);
301
+ return {
302
+ field: raw,
303
+ writers: Array.from(writersByField.get(key) ?? []).sort(),
304
+ ...offSurfaceWriters && offSurfaceWriters.length > 0 ? { offSurfaceWriters } : {}
305
+ };
306
+ });
279
307
  const unwrittenFields = fieldCoverage.filter((f) => f.writers.length === 0).map((f) => f.field);
280
308
  const declaredKeys = new Set(declaredOrder.map((d) => d.key));
281
309
  const unconstrainedMutators = [];
@@ -291,18 +319,24 @@ function computeModelCoverage(stateFields, handlers) {
291
319
  });
292
320
  }
293
321
  }
322
+ const hasDeclaredOffSurface = offSurfaceMutations.some((m) => m.declared);
294
323
  return {
295
324
  fieldCoverage,
296
325
  unwrittenFields,
297
326
  unconstrainedMutators,
298
- hasStrictViolation: unwrittenFields.length > 0
327
+ offSurfaceMutations,
328
+ hasStrictViolation: unwrittenFields.length > 0 || hasDeclaredOffSurface
299
329
  };
300
330
  }
301
331
  function strictCoverageReasons(report, meshFindingCount) {
302
332
  const reasons = [];
303
- if (report.hasStrictViolation) {
333
+ if (report.unwrittenFields.length > 0) {
304
334
  reasons.push(`${report.unwrittenFields.length} declared state field(s) written by no modelled handler`);
305
335
  }
336
+ const declaredOffSurface = (report.offSurfaceMutations ?? []).filter((m) => m.declared);
337
+ if (declaredOffSurface.length > 0) {
338
+ reasons.push(`${declaredOffSurface.length} declared state field write(s) outside any modelled transition (polly#163)`);
339
+ }
306
340
  if (meshFindingCount > 0) {
307
341
  reasons.push(`${meshFindingCount} unverified $meshState/$peerState predicate(s)`);
308
342
  }
@@ -3356,18 +3390,45 @@ var init_witness = __esm(() => {
3356
3390
  // tools/verify/src/codegen/witness.ts
3357
3391
  var exports_witness2 = {};
3358
3392
  __export(exports_witness2, {
3393
+ witnessVerdict: () => witnessVerdict,
3394
+ witnessSpecLocation: () => witnessSpecLocation,
3395
+ witnessPolarity: () => witnessPolarity,
3359
3396
  routeWitness: () => routeWitness,
3397
+ parseModuleName: () => parseModuleName,
3360
3398
  buildWitnessModule: () => buildWitnessModule,
3399
+ buildWitnessInvariantBare: () => buildWitnessInvariantBare,
3361
3400
  buildWitnessInvariant: () => buildWitnessInvariant,
3362
3401
  buildWitnessCfg: () => buildWitnessCfg,
3363
3402
  bddPredicateToTLA: () => bddPredicateToTLA,
3403
+ bareFieldRenderer: () => bareFieldRenderer,
3364
3404
  WitnessTranslationError: () => WitnessTranslationError,
3365
3405
  WITNESS_INVARIANT: () => WITNESS_INVARIANT
3366
3406
  });
3367
- function flattenField(path3) {
3368
- return path3.split(".").join("_");
3407
+ import * as path3 from "node:path";
3408
+ function witnessPolarity(tags) {
3409
+ return tags.includes("forbidden") ? "forbidden" : "positive";
3410
+ }
3411
+ function witnessVerdict(polarity, reachable) {
3412
+ if (polarity === "forbidden") {
3413
+ return reachable ? {
3414
+ status: "violated",
3415
+ ok: false,
3416
+ message: "the forbidden state is REACHABLE — a defect; the counterexample is the path to it"
3417
+ } : { status: "excluded", ok: true, message: "the model proves this state unreachable" };
3418
+ }
3419
+ return reachable ? { status: "reachable", ok: true, message: "the model reaches this outcome" } : {
3420
+ status: "unreachable",
3421
+ ok: false,
3422
+ message: "the model proves this outcome impossible (the scenario lies)"
3423
+ };
3424
+ }
3425
+ function flattenField(path4) {
3426
+ return path4.split(".").join("_");
3369
3427
  }
3370
- function translateOperand(raw) {
3428
+ function bareFieldRenderer(fields = {}) {
3429
+ return (fieldPath) => fields[fieldPath] ?? flattenField(fieldPath);
3430
+ }
3431
+ function translateOperand(raw, render) {
3371
3432
  const op = raw.trim();
3372
3433
  if (op === "true")
3373
3434
  return "TRUE";
@@ -3383,11 +3444,11 @@ function translateOperand(raw) {
3383
3444
  throw new WitnessTranslationError(`unsupported operand "${raw}" in witness predicate`);
3384
3445
  }
3385
3446
  if (op.endsWith(".length")) {
3386
- return `Len(contextStates[ctx].${flattenField(op.slice(0, -".length".length))})`;
3447
+ return `Len(${render(op.slice(0, -".length".length))})`;
3387
3448
  }
3388
- return `contextStates[ctx].${flattenField(op)}`;
3449
+ return render(op);
3389
3450
  }
3390
- function translateConjunct(conjunct) {
3451
+ function translateConjunct(conjunct, render) {
3391
3452
  const text = conjunct.trim();
3392
3453
  for (const [js, tla] of COMPARATORS) {
3393
3454
  const at = text.indexOf(js);
@@ -3395,31 +3456,59 @@ function translateConjunct(conjunct) {
3395
3456
  continue;
3396
3457
  const lhs = text.slice(0, at);
3397
3458
  const rhs = text.slice(at + js.length);
3398
- return `${translateOperand(lhs)} ${tla} ${translateOperand(rhs)}`;
3459
+ return `${translateOperand(lhs, render)} ${tla} ${translateOperand(rhs, render)}`;
3399
3460
  }
3400
3461
  throw new WitnessTranslationError(`no comparison operator in witness conjunct "${conjunct}"`);
3401
3462
  }
3402
- function bddPredicateToTLA(predicate) {
3463
+ function bddPredicateToTLA(predicate, render = contextFieldRenderer) {
3403
3464
  const conjuncts = predicate.split("&&").map((c) => c.trim()).filter(Boolean);
3404
3465
  if (conjuncts.length === 0) {
3405
3466
  throw new WitnessTranslationError("empty witness predicate");
3406
3467
  }
3407
- return conjuncts.map(translateConjunct).join(" /\\ ");
3468
+ return conjuncts.map((c) => translateConjunct(c, render)).join(" /\\ ");
3408
3469
  }
3409
3470
  function buildWitnessInvariant(tlaPredicate) {
3410
3471
  return `${WITNESS_INVARIANT} == ~(\\E ctx \\in Contexts : ${tlaPredicate})`;
3411
3472
  }
3412
- function buildWitnessModule(moduleName, subsystemModule, tlaPredicate) {
3473
+ function buildWitnessInvariantBare(tlaPredicate) {
3474
+ return `${WITNESS_INVARIANT} == ~(${tlaPredicate})`;
3475
+ }
3476
+ function buildWitnessModule(moduleName, baseModule, tlaPredicate, options = {}) {
3477
+ const invariant = options.bare ? buildWitnessInvariantBare(tlaPredicate) : buildWitnessInvariant(tlaPredicate);
3413
3478
  return [
3414
3479
  `---- MODULE ${moduleName} ----`,
3415
- `EXTENDS ${subsystemModule}`,
3480
+ `EXTENDS ${baseModule}`,
3416
3481
  "",
3417
- buildWitnessInvariant(tlaPredicate),
3482
+ invariant,
3418
3483
  "====",
3419
3484
  ""
3420
3485
  ].join(`
3421
3486
  `);
3422
3487
  }
3488
+ function parseModuleName(tlaText) {
3489
+ const m = /^-+\s*MODULE\s+([A-Za-z_]\w*)/m.exec(tlaText);
3490
+ return m?.[1] ?? null;
3491
+ }
3492
+ function witnessSpecLocation(cwd, subsystem, custom, customModule) {
3493
+ if (custom) {
3494
+ const tlaPath = path3.resolve(cwd, custom.tla);
3495
+ return {
3496
+ dir: path3.dirname(tlaPath),
3497
+ tlaPath,
3498
+ cfgPath: path3.resolve(cwd, custom.cfg),
3499
+ module: customModule,
3500
+ custom: true
3501
+ };
3502
+ }
3503
+ const dir = path3.join(cwd, "specs", "tla", "generated", subsystem);
3504
+ return {
3505
+ dir,
3506
+ tlaPath: path3.join(dir, `UserApp_${subsystem}.tla`),
3507
+ cfgPath: path3.join(dir, `UserApp_${subsystem}.cfg`),
3508
+ module: `UserApp_${subsystem}`,
3509
+ custom: false
3510
+ };
3511
+ }
3423
3512
  function sectionBody(cfg, header) {
3424
3513
  const body = [];
3425
3514
  let inSection = false;
@@ -3448,11 +3537,22 @@ function headerLine(cfg, header) {
3448
3537
  return null;
3449
3538
  }
3450
3539
  function buildWitnessCfg(baseCfg) {
3451
- const spec = headerLine(baseCfg, "SPECIFICATION") ?? "SPECIFICATION UserSpec";
3540
+ const spec = headerLine(baseCfg, "SPECIFICATION");
3541
+ const init = headerLine(baseCfg, "INIT");
3542
+ const next = headerLine(baseCfg, "NEXT");
3543
+ const behaviour = spec ? [spec] : init && next ? [init, next] : ["SPECIFICATION UserSpec"];
3452
3544
  const constants = sectionBody(baseCfg, "CONSTANTS");
3453
3545
  const constraint = sectionBody(baseCfg, "CONSTRAINT");
3454
3546
  const symmetry = sectionBody(baseCfg, "SYMMETRY");
3455
- const out = [spec, "", "CONSTANTS", ...constants, "", "INVARIANTS", ` ${WITNESS_INVARIANT}`];
3547
+ const out = [
3548
+ ...behaviour,
3549
+ "",
3550
+ "CONSTANTS",
3551
+ ...constants,
3552
+ "",
3553
+ "INVARIANTS",
3554
+ ` ${WITNESS_INVARIANT}`
3555
+ ];
3456
3556
  if (constraint.length > 0)
3457
3557
  out.push("", "CONSTRAINT", ...constraint);
3458
3558
  if (symmetry.length > 0)
@@ -3471,7 +3571,7 @@ function routeWitness(fields, subsystems) {
3471
3571
  const only = owners.length === 1 ? owners[0] : undefined;
3472
3572
  return only ? only[0] : null;
3473
3573
  }
3474
- var WITNESS_INVARIANT = "WitnessReachable", WitnessTranslationError, COMPARATORS;
3574
+ var WITNESS_INVARIANT = "WitnessReachable", WitnessTranslationError, COMPARATORS, contextFieldRenderer = (fieldPath) => `contextStates[ctx].${flattenField(fieldPath)}`;
3475
3575
  var init_witness2 = __esm(() => {
3476
3576
  WitnessTranslationError = class WitnessTranslationError extends Error {
3477
3577
  };
@@ -3494,11 +3594,11 @@ __export(exports_docker, {
3494
3594
  });
3495
3595
  import { spawn } from "node:child_process";
3496
3596
  import * as fs3 from "node:fs";
3497
- import * as path3 from "node:path";
3597
+ import * as path4 from "node:path";
3498
3598
 
3499
3599
  class DockerRunner {
3500
3600
  IMAGE_NAME = "polly-tla:latest";
3501
- DOCKERFILE_PATH = path3.join(__dirname, "../../Dockerfile");
3601
+ DOCKERFILE_PATH = path4.join(__dirname, "../../Dockerfile");
3502
3602
  async isDockerAvailable() {
3503
3603
  try {
3504
3604
  const result = await this.runCommand("docker", ["info"], {
@@ -3526,7 +3626,7 @@ class DockerRunner {
3526
3626
  }
3527
3627
  }
3528
3628
  async buildImage(onProgress) {
3529
- const dockerfileDir = path3.dirname(this.DOCKERFILE_PATH);
3629
+ const dockerfileDir = path4.dirname(this.DOCKERFILE_PATH);
3530
3630
  await this.runCommandStreaming("docker", ["build", "-f", this.DOCKERFILE_PATH, "-t", this.IMAGE_NAME, dockerfileDir], onProgress, 300000);
3531
3631
  }
3532
3632
  async pullImage(onProgress) {
@@ -3536,13 +3636,13 @@ class DockerRunner {
3536
3636
  if (!fs3.existsSync(specPath)) {
3537
3637
  throw new Error(`Spec file not found: ${specPath}`);
3538
3638
  }
3539
- const specDir = path3.dirname(specPath);
3540
- const specName = path3.basename(specPath, ".tla");
3541
- const cfgPath = path3.join(specDir, `${specName}.cfg`);
3639
+ const specDir = path4.dirname(specPath);
3640
+ const specName = path4.basename(specPath, ".tla");
3641
+ const cfgPath = path4.join(specDir, `${specName}.cfg`);
3542
3642
  if (!fs3.existsSync(cfgPath)) {
3543
3643
  throw new Error(`Config file not found: ${cfgPath}`);
3544
3644
  }
3545
- const statesDir = path3.join(specDir, "states");
3645
+ const statesDir = path4.join(specDir, "states");
3546
3646
  if (fs3.existsSync(statesDir)) {
3547
3647
  fs3.rmSync(statesDir, { recursive: true, force: true });
3548
3648
  }
@@ -3635,7 +3735,7 @@ class DockerRunner {
3635
3735
  return "Unknown error occurred during model checking";
3636
3736
  }
3637
3737
  runCommand(command, args, options) {
3638
- return new Promise((resolve2, reject) => {
3738
+ return new Promise((resolve3, reject) => {
3639
3739
  const proc = spawn(command, args);
3640
3740
  let stdout = "";
3641
3741
  let stderr = "";
@@ -3653,7 +3753,7 @@ class DockerRunner {
3653
3753
  proc.on("close", (exitCode) => {
3654
3754
  if (timeout)
3655
3755
  clearTimeout(timeout);
3656
- resolve2({
3756
+ resolve3({
3657
3757
  exitCode: exitCode || 0,
3658
3758
  stdout,
3659
3759
  stderr
@@ -3667,7 +3767,7 @@ class DockerRunner {
3667
3767
  });
3668
3768
  }
3669
3769
  runCommandStreaming(command, args, onOutput, timeout) {
3670
- return new Promise((resolve2, reject) => {
3770
+ return new Promise((resolve3, reject) => {
3671
3771
  const proc = spawn(command, args);
3672
3772
  const timeoutHandle = timeout && timeout > 0 ? setTimeout(() => {
3673
3773
  proc.kill();
@@ -3699,7 +3799,7 @@ class DockerRunner {
3699
3799
  if (timeoutHandle)
3700
3800
  clearTimeout(timeoutHandle);
3701
3801
  if (exitCode === 0) {
3702
- resolve2();
3802
+ resolve3();
3703
3803
  } else {
3704
3804
  reject(new Error(`Command failed with exit code ${exitCode}`));
3705
3805
  }
@@ -3718,7 +3818,7 @@ var init_docker = () => {};
3718
3818
  // tools/verify/src/cli.ts
3719
3819
  init_expression_validator();
3720
3820
  import * as fs4 from "node:fs";
3721
- import * as path4 from "node:path";
3821
+ import * as path5 from "node:path";
3722
3822
 
3723
3823
  // tools/verify/src/analysis/mesh-signal-warnings.ts
3724
3824
  function computeMeshOrPeerSignalFindings(analysis, declaredMeshDocs) {
@@ -5445,6 +5545,7 @@ class HandlerExtractor {
5445
5545
  warnings;
5446
5546
  currentFunctionParams = [];
5447
5547
  contextOverrides;
5548
+ onSurfaceSpans = [];
5448
5549
  constructor(tsConfigPath, contextOverrides) {
5449
5550
  this.project = new Project({
5450
5551
  tsConfigFilePath: tsConfigPath
@@ -5504,6 +5605,7 @@ class HandlerExtractor {
5504
5605
  const meshOrPeerSignals = [];
5505
5606
  const resources = [];
5506
5607
  this.warnings = [];
5608
+ this.onSurfaceSpans = [];
5507
5609
  const allSourceFiles = this.project.getSourceFiles();
5508
5610
  const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
5509
5611
  this.debugLogSourceFiles(allSourceFiles, entryPoints);
@@ -5538,6 +5640,30 @@ class HandlerExtractor {
5538
5640
  continue;
5539
5641
  meshOrPeerSignals.push(...this.extractMeshOrPeerSignalsFromFile(sourceFile));
5540
5642
  }
5643
+ const stateSignalNames = new Set;
5644
+ for (const filePath of this.analyzedFiles) {
5645
+ const sourceFile = this.project.getSourceFile(filePath);
5646
+ if (!sourceFile)
5647
+ continue;
5648
+ for (const name of this.extractStateSignalVariableNames(sourceFile)) {
5649
+ stateSignalNames.add(name);
5650
+ }
5651
+ }
5652
+ for (const v of verifiedStates)
5653
+ stateSignalNames.add(v.variableName);
5654
+ for (const m of meshOrPeerSignals)
5655
+ stateSignalNames.add(m.variableName);
5656
+ const offSurfaceMutations = [];
5657
+ if (stateSignalNames.size > 0) {
5658
+ for (const filePath of this.analyzedFiles) {
5659
+ if (!this.isOffSurfaceScannable(filePath))
5660
+ continue;
5661
+ const sourceFile = this.project.getSourceFile(filePath);
5662
+ if (!sourceFile)
5663
+ continue;
5664
+ offSurfaceMutations.push(...this.findOffSurfaceMutations(sourceFile, stateSignalNames));
5665
+ }
5666
+ }
5541
5667
  this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
5542
5668
  this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
5543
5669
  return {
@@ -5548,6 +5674,7 @@ class HandlerExtractor {
5548
5674
  verifiedStates,
5549
5675
  meshOrPeerSignals,
5550
5676
  resources,
5677
+ offSurfaceMutations,
5551
5678
  warnings: this.warnings
5552
5679
  };
5553
5680
  }
@@ -5958,6 +6085,7 @@ class HandlerExtractor {
5958
6085
  return false;
5959
6086
  }
5960
6087
  extractAssignments(funcNode, assignments) {
6088
+ this.recordOnSurfaceSpan(funcNode);
5961
6089
  funcNode.forEachDescendant((node) => {
5962
6090
  if (Node2.isBinaryExpression(node)) {
5963
6091
  this.extractBinaryExpressionAssignment(node, assignments);
@@ -7544,6 +7672,7 @@ class HandlerExtractor {
7544
7672
  }
7545
7673
  findStateMutationsInFunction(func, stateVarNames) {
7546
7674
  const mutations = [];
7675
+ this.recordOnSurfaceSpan(func);
7547
7676
  func.forEachDescendant((node) => {
7548
7677
  if (!Node2.isBinaryExpression(node))
7549
7678
  return;
@@ -7573,6 +7702,140 @@ class HandlerExtractor {
7573
7702
  });
7574
7703
  return mutations;
7575
7704
  }
7705
+ isOffSurfaceScannable(filePath) {
7706
+ return !/(?:\.(?:test|spec|stories)\.[cm]?[jt]sx?$)|(?:\/(?:__tests__|tests|test|features|e2e|stories|__mocks__)\/)/i.test(filePath);
7707
+ }
7708
+ static STATE_SIGNAL_FACTORIES = new Set([
7709
+ "$state",
7710
+ "$sharedState",
7711
+ "$syncedState",
7712
+ "$persistedState",
7713
+ "$meshState",
7714
+ "$peerState"
7715
+ ]);
7716
+ extractStateSignalVariableNames(sourceFile) {
7717
+ const names = [];
7718
+ sourceFile.forEachDescendant((node) => {
7719
+ if (!Node2.isCallExpression(node))
7720
+ return;
7721
+ const expr = node.getExpression();
7722
+ if (!Node2.isIdentifier(expr))
7723
+ return;
7724
+ if (!HandlerExtractor.STATE_SIGNAL_FACTORIES.has(expr.getText()))
7725
+ return;
7726
+ const varName = this.getVariableNameFromParent(node);
7727
+ if (varName)
7728
+ names.push(varName);
7729
+ });
7730
+ return names;
7731
+ }
7732
+ recordOnSurfaceSpan(node) {
7733
+ this.onSurfaceSpans.push({
7734
+ file: node.getSourceFile().getFilePath(),
7735
+ start: node.getStart(),
7736
+ end: node.getEnd()
7737
+ });
7738
+ }
7739
+ isWithinOnSurfaceSpan(file, pos) {
7740
+ for (const span of this.onSurfaceSpans) {
7741
+ if (span.file === file && pos >= span.start && pos <= span.end)
7742
+ return true;
7743
+ }
7744
+ return false;
7745
+ }
7746
+ findOffSurfaceMutations(sourceFile, stateVarNames) {
7747
+ const out = [];
7748
+ const filePath = sourceFile.getFilePath();
7749
+ sourceFile.forEachDescendant((node) => {
7750
+ if (Node2.isBinaryExpression(node)) {
7751
+ out.push(...this.offSurfaceWritesAt(node, stateVarNames, filePath));
7752
+ }
7753
+ });
7754
+ return out;
7755
+ }
7756
+ offSurfaceWritesAt(node, stateVarNames, filePath) {
7757
+ if (node.getOperatorToken().getText() !== "=")
7758
+ return [];
7759
+ const left = node.getLeft();
7760
+ if (!Node2.isPropertyAccessExpression(left))
7761
+ return [];
7762
+ const match = this.matchVerifiedStateWrite(this.getPropertyPath(left), stateVarNames);
7763
+ if (!match)
7764
+ return [];
7765
+ if (this.isWithinOnSurfaceSpan(filePath, left.getStart()))
7766
+ return [];
7767
+ const base = {
7768
+ signalVariable: match.signal,
7769
+ functionName: this.enclosingFunctionName(node),
7770
+ filePath,
7771
+ line: node.getStartLineNumber()
7772
+ };
7773
+ if (match.field !== undefined) {
7774
+ return [{ field: `${match.signal}_${match.field}`, ...base }];
7775
+ }
7776
+ const fields = this.objectLiteralFieldNames(node.getRight());
7777
+ if (fields.length === 0)
7778
+ return [{ field: match.signal, ...base }];
7779
+ return fields.map((f) => ({ field: `${match.signal}_${f}`, ...base }));
7780
+ }
7781
+ matchVerifiedStateWrite(path2, stateVarNames) {
7782
+ for (const varName of stateVarNames) {
7783
+ if (path2 === `${varName}.value`)
7784
+ return { signal: varName };
7785
+ const prefix = `${varName}.value.`;
7786
+ if (path2.startsWith(prefix))
7787
+ return { signal: varName, field: path2.substring(prefix.length) };
7788
+ }
7789
+ return null;
7790
+ }
7791
+ objectLiteralFieldNames(right) {
7792
+ if (!Node2.isObjectLiteralExpression(right))
7793
+ return [];
7794
+ const names = [];
7795
+ for (const prop of right.getProperties()) {
7796
+ if (Node2.isPropertyAssignment(prop) || Node2.isShorthandPropertyAssignment(prop)) {
7797
+ names.push(prop.getName());
7798
+ }
7799
+ }
7800
+ return names;
7801
+ }
7802
+ enclosingFunctionName(node) {
7803
+ let current = node.getParent();
7804
+ while (current) {
7805
+ const name = this.namedScope(current);
7806
+ if (name !== null)
7807
+ return name;
7808
+ current = current.getParent();
7809
+ }
7810
+ return "<module>";
7811
+ }
7812
+ namedScope(node) {
7813
+ if (Node2.isFunctionDeclaration(node)) {
7814
+ return node.getName() ?? "<anonymous function>";
7815
+ }
7816
+ if (Node2.isMethodDeclaration(node)) {
7817
+ const clsName = node.getFirstAncestorByKind(SyntaxKind.ClassDeclaration)?.getName();
7818
+ const methodName = node.getName();
7819
+ return clsName ? `${clsName}.${methodName}` : methodName;
7820
+ }
7821
+ if (Node2.isGetAccessorDeclaration(node) || Node2.isSetAccessorDeclaration(node)) {
7822
+ return node.getName();
7823
+ }
7824
+ if (Node2.isArrowFunction(node) || Node2.isFunctionExpression(node)) {
7825
+ return this.boundFunctionName(node) ?? null;
7826
+ }
7827
+ return null;
7828
+ }
7829
+ boundFunctionName(fn) {
7830
+ const parent = fn.getParent();
7831
+ if (!parent)
7832
+ return;
7833
+ if (Node2.isVariableDeclaration(parent))
7834
+ return parent.getName();
7835
+ if (Node2.isPropertyAssignment(parent))
7836
+ return parent.getName();
7837
+ return;
7838
+ }
7576
7839
  functionNameToMessageType(funcName) {
7577
7840
  let name = funcName.replace(/^handle/, "").replace(/^on/, "").replace(/^set/, "Set").replace(/^update/, "Update").replace(/^do/, "");
7578
7841
  if (name.length > 0) {
@@ -7737,7 +8000,8 @@ class TypeExtractor {
7737
8000
  globalStateConstraints: handlerAnalysis.globalStateConstraints,
7738
8001
  verifiedStates: handlerAnalysis.verifiedStates,
7739
8002
  meshOrPeerSignals: handlerAnalysis.meshOrPeerSignals,
7740
- resources: handlerAnalysis.resources
8003
+ resources: handlerAnalysis.resources,
8004
+ offSurfaceMutations: handlerAnalysis.offSurfaceMutations
7741
8005
  };
7742
8006
  }
7743
8007
  extractHandlerAnalysis() {
@@ -8208,7 +8472,7 @@ async function setupCommand() {
8208
8472
  displayAnalysisResults(analysis);
8209
8473
  displayAnalysisSummary(analysis);
8210
8474
  const configContent = generateConfig(analysis);
8211
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8475
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8212
8476
  writeConfigFile(configPath, configContent);
8213
8477
  displaySetupSuccess(configPath);
8214
8478
  } catch (_error) {
@@ -8256,7 +8520,7 @@ function getFieldStatus(confidence) {
8256
8520
  return color("⚠ Manual config", COLORS.red);
8257
8521
  }
8258
8522
  function writeConfigFile(configPath, configContent) {
8259
- const configDir = path4.dirname(configPath);
8523
+ const configDir = path5.dirname(configPath);
8260
8524
  if (!fs4.existsSync(configDir)) {
8261
8525
  fs4.mkdirSync(configDir, { recursive: true });
8262
8526
  }
@@ -8279,7 +8543,7 @@ function displaySetupSuccess(configPath) {
8279
8543
  console.log();
8280
8544
  }
8281
8545
  async function validateCommand() {
8282
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8546
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8283
8547
  console.log(color(`
8284
8548
  \uD83D\uDD0D Validating configuration...
8285
8549
  `, COLORS.blue));
@@ -8300,7 +8564,7 @@ async function validateCommand() {
8300
8564
  process.exit(1);
8301
8565
  }
8302
8566
  async function estimateCommand() {
8303
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8567
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8304
8568
  console.log(color(`
8305
8569
  \uD83D\uDCCA Estimating state space...
8306
8570
  `, COLORS.blue));
@@ -8443,8 +8707,8 @@ function isWitnessMode() {
8443
8707
  return process.argv.includes("--witness");
8444
8708
  }
8445
8709
  function displayModelCoverage(report) {
8446
- const { unwrittenFields, unconstrainedMutators, fieldCoverage } = report;
8447
- if (unwrittenFields.length === 0 && unconstrainedMutators.length === 0) {
8710
+ const { unwrittenFields, unconstrainedMutators, fieldCoverage, offSurfaceMutations } = report;
8711
+ if (unwrittenFields.length === 0 && unconstrainedMutators.length === 0 && offSurfaceMutations.length === 0) {
8448
8712
  console.log(color(`✓ Model coverage: all ${fieldCoverage.length} declared field(s) written by a modelled handler`, COLORS.green));
8449
8713
  console.log();
8450
8714
  return;
@@ -8468,11 +8732,24 @@ function displayModelCoverage(report) {
8468
8732
  console.log(color(" The checker explores these transitions but asserts nothing about their effect.", COLORS.gray));
8469
8733
  console.log();
8470
8734
  }
8735
+ if (offSurfaceMutations.length > 0) {
8736
+ console.log(color(`
8737
+ ⚠️ ${offSurfaceMutations.length} declared state field write(s) outside any modelled transition (polly#163):`, COLORS.yellow));
8738
+ for (const m of offSurfaceMutations) {
8739
+ console.log(color(` • ${m.field} mutated in ${m.function}() — ${m.file}:${m.line}`, COLORS.yellow));
8740
+ }
8741
+ console.log(color(" A non-dispatched path (a method, a non-exported function, a closure) writes", COLORS.gray));
8742
+ console.log(color(" verified state the checker never explores — the register() shape (#160). Even", COLORS.gray));
8743
+ console.log(color(" when a handler also writes the field, model coverage cannot see this writer.", COLORS.gray));
8744
+ console.log(color(" Route the change through a dispatched handler, or drop the field from the", COLORS.gray));
8745
+ console.log(color(" verified surface. See tools/verify/OFF-SURFACE-MUTATORS.md.", COLORS.gray));
8746
+ console.log();
8747
+ }
8471
8748
  }
8472
8749
  async function runModelCoverage(typedConfig, typedAnalysis, meshFindingCount) {
8473
8750
  const { computeModelCoverage: computeModelCoverage2, strictCoverageReasons: strictCoverageReasons2 } = await Promise.resolve().then(() => exports_model_coverage);
8474
8751
  const stateFields = Object.keys(typedConfig.state ?? {});
8475
- const coverage = computeModelCoverage2(stateFields, typedAnalysis.handlers);
8752
+ const coverage = computeModelCoverage2(stateFields, typedAnalysis.handlers, typedAnalysis.offSurfaceMutations ?? []);
8476
8753
  displayModelCoverage(coverage);
8477
8754
  if (!isStrictMode())
8478
8755
  return;
@@ -8494,7 +8771,7 @@ async function runModelCoverage(typedConfig, typedAnalysis, meshFindingCount) {
8494
8771
  process.exit(1);
8495
8772
  }
8496
8773
  async function verifyCommand() {
8497
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8774
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8498
8775
  console.log(color(`
8499
8776
  \uD83D\uDD0D Running verification...
8500
8777
  `, COLORS.blue));
@@ -8560,6 +8837,12 @@ function getMaxDepth(config) {
8560
8837
  }
8561
8838
  return;
8562
8839
  }
8840
+ function getCustomTLAPaths(config) {
8841
+ if ("messages" in config && config.customTLAPaths) {
8842
+ return config.customTLAPaths;
8843
+ }
8844
+ return {};
8845
+ }
8563
8846
  async function runCoupledFieldsLint(config, analysis) {
8564
8847
  const groups = config.coupledFields ?? [];
8565
8848
  if (groups.length === 0)
@@ -8707,18 +8990,23 @@ async function runSubsystemVerification(config, analysis) {
8707
8990
  const maxDepth = getMaxDepth(config);
8708
8991
  const { generateSubsystemTLA: generateSubsystemTLA2 } = await Promise.resolve().then(() => (init_tla(), exports_tla));
8709
8992
  const results = [];
8993
+ const customTLAPaths = getCustomTLAPaths(config);
8710
8994
  for (const name of subsystemNames) {
8711
8995
  const sub = subsystems[name];
8712
8996
  const startTime = Date.now();
8997
+ if (customTLAPaths[name]) {
8998
+ console.log(color(`⏭ ${name}: hand-written spec (customTLAPaths) — witnessed, not generated`, COLORS.gray));
8999
+ continue;
9000
+ }
8713
9001
  console.log(color(`⚙️ Verifying subsystem: ${name}...`, COLORS.blue));
8714
9002
  const { spec, cfg } = await generateSubsystemTLA2(name, sub, config, analysis);
8715
9003
  const ensuresCount = (spec.match(/^EnsuresAfter_\w+ ==/gm) ?? []).length;
8716
- const specDir = path4.join(process.cwd(), "specs", "tla", "generated", name);
9004
+ const specDir = path5.join(process.cwd(), "specs", "tla", "generated", name);
8717
9005
  if (!fs4.existsSync(specDir)) {
8718
9006
  fs4.mkdirSync(specDir, { recursive: true });
8719
9007
  }
8720
- const specPath = path4.join(specDir, `UserApp_${name}.tla`);
8721
- const cfgPath = path4.join(specDir, `UserApp_${name}.cfg`);
9008
+ const specPath = path5.join(specDir, `UserApp_${name}.tla`);
9009
+ const cfgPath = path5.join(specDir, `UserApp_${name}.cfg`);
8722
9010
  fs4.writeFileSync(specPath, spec);
8723
9011
  fs4.writeFileSync(cfgPath, cfg);
8724
9012
  findAndCopyBaseSpec(specDir);
@@ -8747,7 +9035,7 @@ async function runSubsystemVerification(config, analysis) {
8747
9035
  } else if (result.error) {
8748
9036
  console.log(color(` Error: ${result.error}`, COLORS.red));
8749
9037
  }
8750
- fs4.writeFileSync(path4.join(specDir, "tlc-output.log"), result.output);
9038
+ fs4.writeFileSync(path5.join(specDir, "tlc-output.log"), result.output);
8751
9039
  }
8752
9040
  }
8753
9041
  console.log();
@@ -8779,13 +9067,19 @@ async function runWitnessVerification(config) {
8779
9067
  }
8780
9068
  const { extractWitnesses: extractWitnesses2 } = await Promise.resolve().then(() => (init_witness(), exports_witness));
8781
9069
  const {
9070
+ bareFieldRenderer: bareFieldRenderer2,
8782
9071
  bddPredicateToTLA: bddPredicateToTLA2,
8783
9072
  buildWitnessCfg: buildWitnessCfg2,
8784
9073
  buildWitnessModule: buildWitnessModule2,
9074
+ parseModuleName: parseModuleName2,
8785
9075
  routeWitness: routeWitness2,
9076
+ witnessPolarity: witnessPolarity2,
9077
+ witnessSpecLocation: witnessSpecLocation2,
9078
+ witnessVerdict: witnessVerdict2,
8786
9079
  WITNESS_INVARIANT: WITNESS_INVARIANT2
8787
9080
  } = await Promise.resolve().then(() => (init_witness2(), exports_witness2));
8788
9081
  const subsystems = config.subsystems;
9082
+ const customTLAPaths = getCustomTLAPaths(config);
8789
9083
  const witnesses = await extractWitnesses2(featureFiles, stepFiles);
8790
9084
  const docker = await setupDocker();
8791
9085
  const timeoutSeconds = getTimeout(config);
@@ -8795,8 +9089,14 @@ async function runWitnessVerification(config) {
8795
9089
  let idx = 0;
8796
9090
  for (const w of witnesses) {
8797
9091
  const id = `${w.feature} › ${w.scenario}`;
9092
+ const polarity = witnessPolarity2(w.tags);
8798
9093
  if (!w.predicate) {
8799
- results.push({ id, status: "skipped", note: "no state-observable Then to witness" });
9094
+ results.push({
9095
+ id,
9096
+ status: "skipped",
9097
+ ok: true,
9098
+ note: "no state-observable Then to witness"
9099
+ });
8800
9100
  continue;
8801
9101
  }
8802
9102
  const subsystem = routeWitness2(w.fields, subsystems);
@@ -8804,89 +9104,128 @@ async function runWitnessVerification(config) {
8804
9104
  results.push({
8805
9105
  id,
8806
9106
  status: "skipped",
9107
+ ok: true,
8807
9108
  note: `fields [${w.fields.join(", ")}] not owned by a single subsystem`
8808
9109
  });
8809
9110
  continue;
8810
9111
  }
8811
- const specDir = path4.join(cwd, "specs", "tla", "generated", subsystem);
8812
- const subsystemCfg = path4.join(specDir, `UserApp_${subsystem}.cfg`);
8813
- if (!fs4.existsSync(path4.join(specDir, `UserApp_${subsystem}.tla`)) || !fs4.existsSync(subsystemCfg)) {
8814
- results.push({
8815
- id,
8816
- status: "error",
8817
- subsystem,
8818
- note: `subsystem spec missing: ${subsystem}`
8819
- });
8820
- continue;
9112
+ const custom = customTLAPaths[subsystem];
9113
+ let location;
9114
+ if (custom) {
9115
+ const tlaAbs = path5.resolve(cwd, custom.tla);
9116
+ const cfgAbs = path5.resolve(cwd, custom.cfg);
9117
+ if (!fs4.existsSync(tlaAbs) || !fs4.existsSync(cfgAbs)) {
9118
+ results.push({
9119
+ id,
9120
+ status: "error",
9121
+ ok: false,
9122
+ subsystem,
9123
+ note: `custom spec missing: ${custom.tla} / ${custom.cfg}`
9124
+ });
9125
+ continue;
9126
+ }
9127
+ const module = custom.module ?? parseModuleName2(fs4.readFileSync(tlaAbs, "utf8"));
9128
+ if (!module) {
9129
+ results.push({
9130
+ id,
9131
+ status: "error",
9132
+ ok: false,
9133
+ subsystem,
9134
+ note: `cannot read MODULE name from ${custom.tla}; set customTLAPaths.${subsystem}.module`
9135
+ });
9136
+ continue;
9137
+ }
9138
+ location = witnessSpecLocation2(cwd, subsystem, custom, module);
9139
+ } else {
9140
+ location = witnessSpecLocation2(cwd, subsystem, undefined, "");
9141
+ if (!fs4.existsSync(location.tlaPath) || !fs4.existsSync(location.cfgPath)) {
9142
+ results.push({
9143
+ id,
9144
+ status: "error",
9145
+ ok: false,
9146
+ subsystem,
9147
+ note: `subsystem spec missing: ${subsystem}`
9148
+ });
9149
+ continue;
9150
+ }
8821
9151
  }
8822
9152
  let tlaPredicate;
8823
9153
  try {
8824
- tlaPredicate = bddPredicateToTLA2(w.predicate);
9154
+ tlaPredicate = location.custom ? bddPredicateToTLA2(w.predicate, bareFieldRenderer2(custom?.fields)) : bddPredicateToTLA2(w.predicate);
8825
9155
  } catch (err) {
8826
9156
  results.push({
8827
9157
  id,
8828
9158
  status: "error",
9159
+ ok: false,
8829
9160
  subsystem,
8830
9161
  note: err instanceof Error ? err.message : String(err)
8831
9162
  });
8832
9163
  continue;
8833
9164
  }
8834
9165
  const moduleName = `Witness_${subsystem}_${idx++}`;
8835
- const witnessTla = path4.join(specDir, `${moduleName}.tla`);
8836
- fs4.writeFileSync(witnessTla, buildWitnessModule2(moduleName, `UserApp_${subsystem}`, tlaPredicate));
8837
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.cfg`), buildWitnessCfg2(fs4.readFileSync(subsystemCfg, "utf8")));
8838
- console.log(color(`⚙️ ${id}`, COLORS.blue));
9166
+ const witnessTla = path5.join(location.dir, `${moduleName}.tla`);
9167
+ fs4.writeFileSync(witnessTla, buildWitnessModule2(moduleName, location.module, tlaPredicate, { bare: location.custom }));
9168
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.cfg`), buildWitnessCfg2(fs4.readFileSync(location.cfgPath, "utf8")));
9169
+ const polarityTag = polarity === "forbidden" ? color(" [forbidden — must be unreachable]", COLORS.gray) : "";
9170
+ console.log(color(`⚙️ ${id}`, COLORS.blue) + polarityTag);
8839
9171
  console.log(color(` ${subsystem} ⊨ ${w.predicate}`, COLORS.gray));
8840
9172
  let tlc;
8841
9173
  try {
8842
9174
  tlc = await docker.runTLC(witnessTla, { workers, timeout });
8843
9175
  } catch (err) {
8844
9176
  const msg = err instanceof Error ? err.message : String(err);
8845
- results.push({ id, status: "inconclusive", subsystem, predicate: w.predicate, note: msg });
8846
- console.log(color(" ⚠ inconclusive — TLC did not finish (raise the config timeout)", COLORS.yellow));
8847
- continue;
8848
- }
8849
- if (!tlc.success && tlc.violation?.name === WITNESS_INVARIANT2) {
8850
- results.push({ id, status: "reachable", subsystem, predicate: w.predicate });
8851
- console.log(color(" ✓ reachable — the model reaches this outcome", COLORS.green));
8852
- } else if (tlc.success) {
8853
9177
  results.push({
8854
9178
  id,
8855
- status: "unreachable",
9179
+ status: "inconclusive",
9180
+ ok: true,
8856
9181
  subsystem,
8857
9182
  predicate: w.predicate,
8858
- note: `${tlc.stats?.distinctStates ?? 0} distinct states, no path`
9183
+ note: msg
8859
9184
  });
8860
- console.log(color(" UNREACHABLEthe model proves this outcome impossible (the scenario lies)", COLORS.red));
8861
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.tlc-output.log`), tlc.output);
8862
- } else {
9185
+ console.log(color(" inconclusiveTLC did not finish (raise the config timeout)", COLORS.yellow));
9186
+ continue;
9187
+ }
9188
+ const reachable = !tlc.success && tlc.violation?.name === WITNESS_INVARIANT2;
9189
+ if (!tlc.success && !reachable) {
8863
9190
  results.push({
8864
9191
  id,
8865
9192
  status: "error",
9193
+ ok: false,
8866
9194
  subsystem,
8867
9195
  predicate: w.predicate,
8868
9196
  note: tlc.error ?? tlc.violation?.name ?? "TLC error"
8869
9197
  });
8870
9198
  console.log(color(` ! error — ${tlc.error ?? "see log"}`, COLORS.yellow));
8871
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.tlc-output.log`), tlc.output);
9199
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.tlc-output.log`), tlc.output);
9200
+ continue;
8872
9201
  }
9202
+ const verdict = witnessVerdict2(polarity, reachable);
9203
+ const note = reachable ? undefined : `${tlc.stats?.distinctStates ?? 0} distinct states explored`;
9204
+ results.push({
9205
+ id,
9206
+ status: verdict.status,
9207
+ ok: verdict.ok,
9208
+ subsystem,
9209
+ predicate: w.predicate,
9210
+ note
9211
+ });
9212
+ console.log(color(` ${verdict.ok ? "✓" : "✗"} ${verdict.status} — ${verdict.message}`, verdict.ok ? COLORS.green : COLORS.red));
9213
+ if (!verdict.ok)
9214
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.tlc-output.log`), tlc.output);
8873
9215
  }
8874
9216
  displayWitnessReport(results);
8875
9217
  }
8876
9218
  function displayWitnessReport(results) {
8877
9219
  const count = (s) => results.filter((r) => r.status === s).length;
8878
- const reachable = count("reachable");
8879
- const unreachable = count("unreachable");
8880
- const inconclusive = count("inconclusive");
8881
- const errors = count("error");
8882
- const skipped = count("skipped");
8883
9220
  console.log();
8884
9221
  console.log(color(`Witness results:
8885
9222
  `, COLORS.blue));
8886
9223
  for (const r of results) {
8887
9224
  const mark = {
8888
9225
  reachable: color("✓", COLORS.green),
9226
+ excluded: color("✓", COLORS.green),
8889
9227
  unreachable: color("✗", COLORS.red),
9228
+ violated: color("✗", COLORS.red),
8890
9229
  inconclusive: color("⚠", COLORS.yellow),
8891
9230
  error: color("!", COLORS.yellow),
8892
9231
  skipped: color("·", COLORS.gray)
@@ -8894,17 +9233,27 @@ function displayWitnessReport(results) {
8894
9233
  const note = r.note ? color(` (${r.note})`, COLORS.gray) : "";
8895
9234
  console.log(` ${mark} ${r.status.padEnd(12)} ${r.id}${note}`);
8896
9235
  }
9236
+ const tally = [
9237
+ "reachable",
9238
+ "excluded",
9239
+ "unreachable",
9240
+ "violated",
9241
+ "inconclusive",
9242
+ "error",
9243
+ "skipped"
9244
+ ].map((s) => `${count(s)} ${s}`).join(", ");
8897
9245
  console.log();
8898
- console.log(color(` ${reachable} reachable, ${unreachable} unreachable, ${inconclusive} inconclusive, ${errors} error, ${skipped} skipped`, COLORS.gray));
9246
+ console.log(color(` ${tally}`, COLORS.gray));
8899
9247
  console.log();
8900
- if (unreachable > 0 || errors > 0) {
9248
+ const failures = results.filter((r) => !r.ok);
9249
+ if (failures.length > 0) {
8901
9250
  console.log(color("Witness result: ✗ FAIL", COLORS.red));
8902
- console.log(color(" A scenario the exhaustive model cannot reach describes an impossible outcome.", COLORS.gray));
9251
+ console.log(color(" An outcome a scenario claims is unreachable, or a forbidden state the model can reach.", COLORS.gray));
8903
9252
  console.log();
8904
9253
  process.exit(1);
8905
9254
  }
8906
9255
  console.log(color("Witness result: ✓ PASS", COLORS.green));
8907
- console.log(color(" Every witnessable scenario's outcome is reachable in the model.", COLORS.gray));
9256
+ console.log(color(" Every claimed outcome is reachable; every forbidden state is proven unreachable.", COLORS.gray));
8908
9257
  console.log();
8909
9258
  }
8910
9259
  function displayEnsuresSummary(results) {
@@ -8954,7 +9303,7 @@ function displayCompositionalReport(results, nonInterferenceValid) {
8954
9303
  console.log();
8955
9304
  }
8956
9305
  async function loadVerificationConfig(configPath) {
8957
- const resolvedPath = path4.resolve(configPath);
9306
+ const resolvedPath = path5.resolve(configPath);
8958
9307
  const configModule = await import(`file://${resolvedPath}?t=${Date.now()}`);
8959
9308
  return configModule.verificationConfig || configModule.default;
8960
9309
  }
@@ -8976,23 +9325,23 @@ async function generateAndWriteTLASpecs(config, analysis) {
8976
9325
  const { generateTLA: generateTLA2 } = await Promise.resolve().then(() => (init_tla(), exports_tla));
8977
9326
  console.log(color("\uD83D\uDCDD Generating TLA+ specification...", COLORS.blue));
8978
9327
  const { spec, cfg } = await generateTLA2(config, analysis);
8979
- const specDir = path4.join(process.cwd(), "specs", "tla", "generated");
9328
+ const specDir = path5.join(process.cwd(), "specs", "tla", "generated");
8980
9329
  if (!fs4.existsSync(specDir)) {
8981
9330
  fs4.mkdirSync(specDir, { recursive: true });
8982
9331
  }
8983
- const specPath = path4.join(specDir, "UserApp.tla");
8984
- const cfgPath = path4.join(specDir, "UserApp.cfg");
9332
+ const specPath = path5.join(specDir, "UserApp.tla");
9333
+ const cfgPath = path5.join(specDir, "UserApp.cfg");
8985
9334
  fs4.writeFileSync(specPath, spec);
8986
9335
  fs4.writeFileSync(cfgPath, cfg);
8987
9336
  return { specPath, specDir };
8988
9337
  }
8989
9338
  function findAndCopyBaseSpec(specDir) {
8990
9339
  const possiblePaths = [
8991
- path4.join(process.cwd(), "specs", "tla", "MessageRouter.tla"),
8992
- path4.join(__dirname, "..", "specs", "tla", "MessageRouter.tla"),
8993
- path4.join(__dirname, "..", "..", "specs", "tla", "MessageRouter.tla"),
8994
- path4.join(process.cwd(), "external", "polly", "packages", "verify", "specs", "tla", "MessageRouter.tla"),
8995
- path4.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla", "MessageRouter.tla")
9340
+ path5.join(process.cwd(), "specs", "tla", "MessageRouter.tla"),
9341
+ path5.join(__dirname, "..", "specs", "tla", "MessageRouter.tla"),
9342
+ path5.join(__dirname, "..", "..", "specs", "tla", "MessageRouter.tla"),
9343
+ path5.join(process.cwd(), "external", "polly", "packages", "verify", "specs", "tla", "MessageRouter.tla"),
9344
+ path5.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla", "MessageRouter.tla")
8996
9345
  ];
8997
9346
  let baseSpecPath = null;
8998
9347
  for (const candidatePath of possiblePaths) {
@@ -9002,7 +9351,7 @@ function findAndCopyBaseSpec(specDir) {
9002
9351
  }
9003
9352
  }
9004
9353
  if (baseSpecPath) {
9005
- const destSpecPath = path4.join(specDir, "MessageRouter.tla");
9354
+ const destSpecPath = path5.join(specDir, "MessageRouter.tla");
9006
9355
  fs4.copyFileSync(baseSpecPath, destSpecPath);
9007
9356
  console.log(color("✓ Copied MessageRouter.tla", COLORS.green));
9008
9357
  } else {
@@ -9015,13 +9364,13 @@ function findAndCopyBaseSpec(specDir) {
9015
9364
  }
9016
9365
  function findMeshSeedSpecDir() {
9017
9366
  const candidates = [
9018
- path4.join(process.cwd(), "specs", "tla"),
9019
- path4.join(__dirname, "..", "specs", "tla"),
9020
- path4.join(__dirname, "..", "..", "specs", "tla"),
9021
- path4.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla")
9367
+ path5.join(process.cwd(), "specs", "tla"),
9368
+ path5.join(__dirname, "..", "specs", "tla"),
9369
+ path5.join(__dirname, "..", "..", "specs", "tla"),
9370
+ path5.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla")
9022
9371
  ];
9023
9372
  for (const dir of candidates) {
9024
- if (fs4.existsSync(path4.join(dir, "MeshSeed.tla")))
9373
+ if (fs4.existsSync(path5.join(dir, "MeshSeed.tla")))
9025
9374
  return dir;
9026
9375
  }
9027
9376
  return null;
@@ -9035,11 +9384,11 @@ async function runMeshSeedGuard(docker, specDir, config) {
9035
9384
  return;
9036
9385
  }
9037
9386
  const fixDisabled = isSeedFixDisabled();
9038
- fs4.copyFileSync(path4.join(sourceDir, "MeshSeed.tla"), path4.join(specDir, "MeshSeed.tla"));
9039
- const baseCfg = fs4.readFileSync(path4.join(sourceDir, "MeshSeed.cfg"), "utf8");
9040
- fs4.writeFileSync(path4.join(specDir, "MeshSeed.cfg"), meshSeedCfg(baseCfg, { disableFix: fixDisabled }));
9387
+ fs4.copyFileSync(path5.join(sourceDir, "MeshSeed.tla"), path5.join(specDir, "MeshSeed.tla"));
9388
+ const baseCfg = fs4.readFileSync(path5.join(sourceDir, "MeshSeed.cfg"), "utf8");
9389
+ fs4.writeFileSync(path5.join(specDir, "MeshSeed.cfg"), meshSeedCfg(baseCfg, { disableFix: fixDisabled }));
9041
9390
  console.log(color(`⚙️ Running mesh seed-race guard (MeshSeed.tla, SeedDeterministic = ${fixDisabled ? "FALSE" : "TRUE"})...`, COLORS.blue));
9042
- return docker.runTLC(path4.join(specDir, "MeshSeed.tla"), { workers: 1 });
9391
+ return docker.runTLC(path5.join(specDir, "MeshSeed.tla"), { workers: 1 });
9043
9392
  }
9044
9393
  async function setupDocker() {
9045
9394
  const { DockerRunner: DockerRunner2 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
@@ -9121,8 +9470,8 @@ function displayVerificationResults(result, specDir) {
9121
9470
  }
9122
9471
  console.log();
9123
9472
  console.log(color("Full output saved to:", COLORS.gray));
9124
- console.log(color(` ${path4.join(specDir, "tlc-output.log")}`, COLORS.gray));
9125
- fs4.writeFileSync(path4.join(specDir, "tlc-output.log"), result.output);
9473
+ console.log(color(` ${path5.join(specDir, "tlc-output.log")}`, COLORS.gray));
9474
+ fs4.writeFileSync(path5.join(specDir, "tlc-output.log"), result.output);
9126
9475
  process.exit(1);
9127
9476
  }
9128
9477
  function showHelp() {
@@ -9185,8 +9534,8 @@ ${color("Learn More:", COLORS.blue)}
9185
9534
  }
9186
9535
  function findTsConfig() {
9187
9536
  const locations = [
9188
- path4.join(process.cwd(), "tsconfig.json"),
9189
- path4.join(process.cwd(), "packages", "web-ext", "tsconfig.json")
9537
+ path5.join(process.cwd(), "tsconfig.json"),
9538
+ path5.join(process.cwd(), "packages", "web-ext", "tsconfig.json")
9190
9539
  ];
9191
9540
  for (const loc of locations) {
9192
9541
  if (fs4.existsSync(loc)) {
@@ -9197,9 +9546,9 @@ function findTsConfig() {
9197
9546
  }
9198
9547
  function findStateFile() {
9199
9548
  const locations = [
9200
- path4.join(process.cwd(), "types", "state.ts"),
9201
- path4.join(process.cwd(), "src", "types", "state.ts"),
9202
- path4.join(process.cwd(), "packages", "web-ext", "src", "shared", "state", "app-state.ts")
9549
+ path5.join(process.cwd(), "types", "state.ts"),
9550
+ path5.join(process.cwd(), "src", "types", "state.ts"),
9551
+ path5.join(process.cwd(), "packages", "web-ext", "src", "shared", "state", "app-state.ts")
9203
9552
  ];
9204
9553
  for (const loc of locations) {
9205
9554
  if (fs4.existsSync(loc)) {
@@ -9213,4 +9562,4 @@ main().catch((error) => {
9213
9562
  process.exit(1);
9214
9563
  });
9215
9564
 
9216
- //# debugId=006B6ED4C6E5114F64756E2164756E21
9565
+ //# debugId=0E6C5C2B308F1EA464756E2164756E21