@fairfox/polly 0.84.0 → 0.85.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  }
@@ -3357,15 +3391,20 @@ var init_witness = __esm(() => {
3357
3391
  var exports_witness2 = {};
3358
3392
  __export(exports_witness2, {
3359
3393
  witnessVerdict: () => witnessVerdict,
3394
+ witnessSpecLocation: () => witnessSpecLocation,
3360
3395
  witnessPolarity: () => witnessPolarity,
3361
3396
  routeWitness: () => routeWitness,
3397
+ parseModuleName: () => parseModuleName,
3362
3398
  buildWitnessModule: () => buildWitnessModule,
3399
+ buildWitnessInvariantBare: () => buildWitnessInvariantBare,
3363
3400
  buildWitnessInvariant: () => buildWitnessInvariant,
3364
3401
  buildWitnessCfg: () => buildWitnessCfg,
3365
3402
  bddPredicateToTLA: () => bddPredicateToTLA,
3403
+ bareFieldRenderer: () => bareFieldRenderer,
3366
3404
  WitnessTranslationError: () => WitnessTranslationError,
3367
3405
  WITNESS_INVARIANT: () => WITNESS_INVARIANT
3368
3406
  });
3407
+ import * as path3 from "node:path";
3369
3408
  function witnessPolarity(tags) {
3370
3409
  return tags.includes("forbidden") ? "forbidden" : "positive";
3371
3410
  }
@@ -3383,10 +3422,13 @@ function witnessVerdict(polarity, reachable) {
3383
3422
  message: "the model proves this outcome impossible (the scenario lies)"
3384
3423
  };
3385
3424
  }
3386
- function flattenField(path3) {
3387
- return path3.split(".").join("_");
3425
+ function flattenField(path4) {
3426
+ return path4.split(".").join("_");
3388
3427
  }
3389
- function translateOperand(raw) {
3428
+ function bareFieldRenderer(fields = {}) {
3429
+ return (fieldPath) => fields[fieldPath] ?? flattenField(fieldPath);
3430
+ }
3431
+ function translateOperand(raw, render) {
3390
3432
  const op = raw.trim();
3391
3433
  if (op === "true")
3392
3434
  return "TRUE";
@@ -3402,11 +3444,11 @@ function translateOperand(raw) {
3402
3444
  throw new WitnessTranslationError(`unsupported operand "${raw}" in witness predicate`);
3403
3445
  }
3404
3446
  if (op.endsWith(".length")) {
3405
- return `Len(contextStates[ctx].${flattenField(op.slice(0, -".length".length))})`;
3447
+ return `Len(${render(op.slice(0, -".length".length))})`;
3406
3448
  }
3407
- return `contextStates[ctx].${flattenField(op)}`;
3449
+ return render(op);
3408
3450
  }
3409
- function translateConjunct(conjunct) {
3451
+ function translateConjunct(conjunct, render) {
3410
3452
  const text = conjunct.trim();
3411
3453
  for (const [js, tla] of COMPARATORS) {
3412
3454
  const at = text.indexOf(js);
@@ -3414,31 +3456,59 @@ function translateConjunct(conjunct) {
3414
3456
  continue;
3415
3457
  const lhs = text.slice(0, at);
3416
3458
  const rhs = text.slice(at + js.length);
3417
- return `${translateOperand(lhs)} ${tla} ${translateOperand(rhs)}`;
3459
+ return `${translateOperand(lhs, render)} ${tla} ${translateOperand(rhs, render)}`;
3418
3460
  }
3419
3461
  throw new WitnessTranslationError(`no comparison operator in witness conjunct "${conjunct}"`);
3420
3462
  }
3421
- function bddPredicateToTLA(predicate) {
3463
+ function bddPredicateToTLA(predicate, render = contextFieldRenderer) {
3422
3464
  const conjuncts = predicate.split("&&").map((c) => c.trim()).filter(Boolean);
3423
3465
  if (conjuncts.length === 0) {
3424
3466
  throw new WitnessTranslationError("empty witness predicate");
3425
3467
  }
3426
- return conjuncts.map(translateConjunct).join(" /\\ ");
3468
+ return conjuncts.map((c) => translateConjunct(c, render)).join(" /\\ ");
3427
3469
  }
3428
3470
  function buildWitnessInvariant(tlaPredicate) {
3429
3471
  return `${WITNESS_INVARIANT} == ~(\\E ctx \\in Contexts : ${tlaPredicate})`;
3430
3472
  }
3431
- 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);
3432
3478
  return [
3433
3479
  `---- MODULE ${moduleName} ----`,
3434
- `EXTENDS ${subsystemModule}`,
3480
+ `EXTENDS ${baseModule}`,
3435
3481
  "",
3436
- buildWitnessInvariant(tlaPredicate),
3482
+ invariant,
3437
3483
  "====",
3438
3484
  ""
3439
3485
  ].join(`
3440
3486
  `);
3441
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
+ }
3442
3512
  function sectionBody(cfg, header) {
3443
3513
  const body = [];
3444
3514
  let inSection = false;
@@ -3457,6 +3527,28 @@ function sectionBody(cfg, header) {
3457
3527
  }
3458
3528
  return body;
3459
3529
  }
3530
+ function constantBody(cfg) {
3531
+ const body = [];
3532
+ let inSection = false;
3533
+ for (const line of cfg.split(`
3534
+ `)) {
3535
+ const headerMatch = /^([A-Z_]+)\b(.*)$/.exec(line);
3536
+ if (headerMatch) {
3537
+ const isConstant = headerMatch[1] === "CONSTANT" || headerMatch[1] === "CONSTANTS";
3538
+ inSection = isConstant;
3539
+ const inline = (headerMatch[2] ?? "").trim();
3540
+ if (isConstant && inline !== "")
3541
+ body.push(` ${inline}`);
3542
+ continue;
3543
+ }
3544
+ if (!inSection)
3545
+ continue;
3546
+ if (line.trim() === "" || line.trim().startsWith("\\*"))
3547
+ continue;
3548
+ body.push(line);
3549
+ }
3550
+ return body;
3551
+ }
3460
3552
  function headerLine(cfg, header) {
3461
3553
  for (const line of cfg.split(`
3462
3554
  `)) {
@@ -3467,11 +3559,22 @@ function headerLine(cfg, header) {
3467
3559
  return null;
3468
3560
  }
3469
3561
  function buildWitnessCfg(baseCfg) {
3470
- const spec = headerLine(baseCfg, "SPECIFICATION") ?? "SPECIFICATION UserSpec";
3471
- const constants = sectionBody(baseCfg, "CONSTANTS");
3562
+ const spec = headerLine(baseCfg, "SPECIFICATION");
3563
+ const init = headerLine(baseCfg, "INIT");
3564
+ const next = headerLine(baseCfg, "NEXT");
3565
+ const behaviour = spec ? [spec] : init && next ? [init, next] : ["SPECIFICATION UserSpec"];
3566
+ const constants = constantBody(baseCfg);
3472
3567
  const constraint = sectionBody(baseCfg, "CONSTRAINT");
3473
3568
  const symmetry = sectionBody(baseCfg, "SYMMETRY");
3474
- const out = [spec, "", "CONSTANTS", ...constants, "", "INVARIANTS", ` ${WITNESS_INVARIANT}`];
3569
+ const out = [
3570
+ ...behaviour,
3571
+ "",
3572
+ "CONSTANTS",
3573
+ ...constants,
3574
+ "",
3575
+ "INVARIANTS",
3576
+ ` ${WITNESS_INVARIANT}`
3577
+ ];
3475
3578
  if (constraint.length > 0)
3476
3579
  out.push("", "CONSTRAINT", ...constraint);
3477
3580
  if (symmetry.length > 0)
@@ -3490,7 +3593,7 @@ function routeWitness(fields, subsystems) {
3490
3593
  const only = owners.length === 1 ? owners[0] : undefined;
3491
3594
  return only ? only[0] : null;
3492
3595
  }
3493
- var WITNESS_INVARIANT = "WitnessReachable", WitnessTranslationError, COMPARATORS;
3596
+ var WITNESS_INVARIANT = "WitnessReachable", WitnessTranslationError, COMPARATORS, contextFieldRenderer = (fieldPath) => `contextStates[ctx].${flattenField(fieldPath)}`;
3494
3597
  var init_witness2 = __esm(() => {
3495
3598
  WitnessTranslationError = class WitnessTranslationError extends Error {
3496
3599
  };
@@ -3513,11 +3616,11 @@ __export(exports_docker, {
3513
3616
  });
3514
3617
  import { spawn } from "node:child_process";
3515
3618
  import * as fs3 from "node:fs";
3516
- import * as path3 from "node:path";
3619
+ import * as path4 from "node:path";
3517
3620
 
3518
3621
  class DockerRunner {
3519
3622
  IMAGE_NAME = "polly-tla:latest";
3520
- DOCKERFILE_PATH = path3.join(__dirname, "../../Dockerfile");
3623
+ DOCKERFILE_PATH = path4.join(__dirname, "../../Dockerfile");
3521
3624
  async isDockerAvailable() {
3522
3625
  try {
3523
3626
  const result = await this.runCommand("docker", ["info"], {
@@ -3545,7 +3648,7 @@ class DockerRunner {
3545
3648
  }
3546
3649
  }
3547
3650
  async buildImage(onProgress) {
3548
- const dockerfileDir = path3.dirname(this.DOCKERFILE_PATH);
3651
+ const dockerfileDir = path4.dirname(this.DOCKERFILE_PATH);
3549
3652
  await this.runCommandStreaming("docker", ["build", "-f", this.DOCKERFILE_PATH, "-t", this.IMAGE_NAME, dockerfileDir], onProgress, 300000);
3550
3653
  }
3551
3654
  async pullImage(onProgress) {
@@ -3555,13 +3658,13 @@ class DockerRunner {
3555
3658
  if (!fs3.existsSync(specPath)) {
3556
3659
  throw new Error(`Spec file not found: ${specPath}`);
3557
3660
  }
3558
- const specDir = path3.dirname(specPath);
3559
- const specName = path3.basename(specPath, ".tla");
3560
- const cfgPath = path3.join(specDir, `${specName}.cfg`);
3661
+ const specDir = path4.dirname(specPath);
3662
+ const specName = path4.basename(specPath, ".tla");
3663
+ const cfgPath = path4.join(specDir, `${specName}.cfg`);
3561
3664
  if (!fs3.existsSync(cfgPath)) {
3562
3665
  throw new Error(`Config file not found: ${cfgPath}`);
3563
3666
  }
3564
- const statesDir = path3.join(specDir, "states");
3667
+ const statesDir = path4.join(specDir, "states");
3565
3668
  if (fs3.existsSync(statesDir)) {
3566
3669
  fs3.rmSync(statesDir, { recursive: true, force: true });
3567
3670
  }
@@ -3654,7 +3757,7 @@ class DockerRunner {
3654
3757
  return "Unknown error occurred during model checking";
3655
3758
  }
3656
3759
  runCommand(command, args, options) {
3657
- return new Promise((resolve2, reject) => {
3760
+ return new Promise((resolve3, reject) => {
3658
3761
  const proc = spawn(command, args);
3659
3762
  let stdout = "";
3660
3763
  let stderr = "";
@@ -3672,7 +3775,7 @@ class DockerRunner {
3672
3775
  proc.on("close", (exitCode) => {
3673
3776
  if (timeout)
3674
3777
  clearTimeout(timeout);
3675
- resolve2({
3778
+ resolve3({
3676
3779
  exitCode: exitCode || 0,
3677
3780
  stdout,
3678
3781
  stderr
@@ -3686,7 +3789,7 @@ class DockerRunner {
3686
3789
  });
3687
3790
  }
3688
3791
  runCommandStreaming(command, args, onOutput, timeout) {
3689
- return new Promise((resolve2, reject) => {
3792
+ return new Promise((resolve3, reject) => {
3690
3793
  const proc = spawn(command, args);
3691
3794
  const timeoutHandle = timeout && timeout > 0 ? setTimeout(() => {
3692
3795
  proc.kill();
@@ -3718,7 +3821,7 @@ class DockerRunner {
3718
3821
  if (timeoutHandle)
3719
3822
  clearTimeout(timeoutHandle);
3720
3823
  if (exitCode === 0) {
3721
- resolve2();
3824
+ resolve3();
3722
3825
  } else {
3723
3826
  reject(new Error(`Command failed with exit code ${exitCode}`));
3724
3827
  }
@@ -3737,7 +3840,7 @@ var init_docker = () => {};
3737
3840
  // tools/verify/src/cli.ts
3738
3841
  init_expression_validator();
3739
3842
  import * as fs4 from "node:fs";
3740
- import * as path4 from "node:path";
3843
+ import * as path5 from "node:path";
3741
3844
 
3742
3845
  // tools/verify/src/analysis/mesh-signal-warnings.ts
3743
3846
  function computeMeshOrPeerSignalFindings(analysis, declaredMeshDocs) {
@@ -5464,6 +5567,7 @@ class HandlerExtractor {
5464
5567
  warnings;
5465
5568
  currentFunctionParams = [];
5466
5569
  contextOverrides;
5570
+ onSurfaceSpans = [];
5467
5571
  constructor(tsConfigPath, contextOverrides) {
5468
5572
  this.project = new Project({
5469
5573
  tsConfigFilePath: tsConfigPath
@@ -5523,6 +5627,7 @@ class HandlerExtractor {
5523
5627
  const meshOrPeerSignals = [];
5524
5628
  const resources = [];
5525
5629
  this.warnings = [];
5630
+ this.onSurfaceSpans = [];
5526
5631
  const allSourceFiles = this.project.getSourceFiles();
5527
5632
  const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
5528
5633
  this.debugLogSourceFiles(allSourceFiles, entryPoints);
@@ -5557,6 +5662,30 @@ class HandlerExtractor {
5557
5662
  continue;
5558
5663
  meshOrPeerSignals.push(...this.extractMeshOrPeerSignalsFromFile(sourceFile));
5559
5664
  }
5665
+ const stateSignalNames = new Set;
5666
+ for (const filePath of this.analyzedFiles) {
5667
+ const sourceFile = this.project.getSourceFile(filePath);
5668
+ if (!sourceFile)
5669
+ continue;
5670
+ for (const name of this.extractStateSignalVariableNames(sourceFile)) {
5671
+ stateSignalNames.add(name);
5672
+ }
5673
+ }
5674
+ for (const v of verifiedStates)
5675
+ stateSignalNames.add(v.variableName);
5676
+ for (const m of meshOrPeerSignals)
5677
+ stateSignalNames.add(m.variableName);
5678
+ const offSurfaceMutations = [];
5679
+ if (stateSignalNames.size > 0) {
5680
+ for (const filePath of this.analyzedFiles) {
5681
+ if (!this.isOffSurfaceScannable(filePath))
5682
+ continue;
5683
+ const sourceFile = this.project.getSourceFile(filePath);
5684
+ if (!sourceFile)
5685
+ continue;
5686
+ offSurfaceMutations.push(...this.findOffSurfaceMutations(sourceFile, stateSignalNames));
5687
+ }
5688
+ }
5560
5689
  this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
5561
5690
  this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
5562
5691
  return {
@@ -5567,6 +5696,7 @@ class HandlerExtractor {
5567
5696
  verifiedStates,
5568
5697
  meshOrPeerSignals,
5569
5698
  resources,
5699
+ offSurfaceMutations,
5570
5700
  warnings: this.warnings
5571
5701
  };
5572
5702
  }
@@ -5977,6 +6107,7 @@ class HandlerExtractor {
5977
6107
  return false;
5978
6108
  }
5979
6109
  extractAssignments(funcNode, assignments) {
6110
+ this.recordOnSurfaceSpan(funcNode);
5980
6111
  funcNode.forEachDescendant((node) => {
5981
6112
  if (Node2.isBinaryExpression(node)) {
5982
6113
  this.extractBinaryExpressionAssignment(node, assignments);
@@ -7563,6 +7694,7 @@ class HandlerExtractor {
7563
7694
  }
7564
7695
  findStateMutationsInFunction(func, stateVarNames) {
7565
7696
  const mutations = [];
7697
+ this.recordOnSurfaceSpan(func);
7566
7698
  func.forEachDescendant((node) => {
7567
7699
  if (!Node2.isBinaryExpression(node))
7568
7700
  return;
@@ -7592,6 +7724,140 @@ class HandlerExtractor {
7592
7724
  });
7593
7725
  return mutations;
7594
7726
  }
7727
+ isOffSurfaceScannable(filePath) {
7728
+ return !/(?:\.(?:test|spec|stories)\.[cm]?[jt]sx?$)|(?:\/(?:__tests__|tests|test|features|e2e|stories|__mocks__)\/)/i.test(filePath);
7729
+ }
7730
+ static STATE_SIGNAL_FACTORIES = new Set([
7731
+ "$state",
7732
+ "$sharedState",
7733
+ "$syncedState",
7734
+ "$persistedState",
7735
+ "$meshState",
7736
+ "$peerState"
7737
+ ]);
7738
+ extractStateSignalVariableNames(sourceFile) {
7739
+ const names = [];
7740
+ sourceFile.forEachDescendant((node) => {
7741
+ if (!Node2.isCallExpression(node))
7742
+ return;
7743
+ const expr = node.getExpression();
7744
+ if (!Node2.isIdentifier(expr))
7745
+ return;
7746
+ if (!HandlerExtractor.STATE_SIGNAL_FACTORIES.has(expr.getText()))
7747
+ return;
7748
+ const varName = this.getVariableNameFromParent(node);
7749
+ if (varName)
7750
+ names.push(varName);
7751
+ });
7752
+ return names;
7753
+ }
7754
+ recordOnSurfaceSpan(node) {
7755
+ this.onSurfaceSpans.push({
7756
+ file: node.getSourceFile().getFilePath(),
7757
+ start: node.getStart(),
7758
+ end: node.getEnd()
7759
+ });
7760
+ }
7761
+ isWithinOnSurfaceSpan(file, pos) {
7762
+ for (const span of this.onSurfaceSpans) {
7763
+ if (span.file === file && pos >= span.start && pos <= span.end)
7764
+ return true;
7765
+ }
7766
+ return false;
7767
+ }
7768
+ findOffSurfaceMutations(sourceFile, stateVarNames) {
7769
+ const out = [];
7770
+ const filePath = sourceFile.getFilePath();
7771
+ sourceFile.forEachDescendant((node) => {
7772
+ if (Node2.isBinaryExpression(node)) {
7773
+ out.push(...this.offSurfaceWritesAt(node, stateVarNames, filePath));
7774
+ }
7775
+ });
7776
+ return out;
7777
+ }
7778
+ offSurfaceWritesAt(node, stateVarNames, filePath) {
7779
+ if (node.getOperatorToken().getText() !== "=")
7780
+ return [];
7781
+ const left = node.getLeft();
7782
+ if (!Node2.isPropertyAccessExpression(left))
7783
+ return [];
7784
+ const match = this.matchVerifiedStateWrite(this.getPropertyPath(left), stateVarNames);
7785
+ if (!match)
7786
+ return [];
7787
+ if (this.isWithinOnSurfaceSpan(filePath, left.getStart()))
7788
+ return [];
7789
+ const base = {
7790
+ signalVariable: match.signal,
7791
+ functionName: this.enclosingFunctionName(node),
7792
+ filePath,
7793
+ line: node.getStartLineNumber()
7794
+ };
7795
+ if (match.field !== undefined) {
7796
+ return [{ field: `${match.signal}_${match.field}`, ...base }];
7797
+ }
7798
+ const fields = this.objectLiteralFieldNames(node.getRight());
7799
+ if (fields.length === 0)
7800
+ return [{ field: match.signal, ...base }];
7801
+ return fields.map((f) => ({ field: `${match.signal}_${f}`, ...base }));
7802
+ }
7803
+ matchVerifiedStateWrite(path2, stateVarNames) {
7804
+ for (const varName of stateVarNames) {
7805
+ if (path2 === `${varName}.value`)
7806
+ return { signal: varName };
7807
+ const prefix = `${varName}.value.`;
7808
+ if (path2.startsWith(prefix))
7809
+ return { signal: varName, field: path2.substring(prefix.length) };
7810
+ }
7811
+ return null;
7812
+ }
7813
+ objectLiteralFieldNames(right) {
7814
+ if (!Node2.isObjectLiteralExpression(right))
7815
+ return [];
7816
+ const names = [];
7817
+ for (const prop of right.getProperties()) {
7818
+ if (Node2.isPropertyAssignment(prop) || Node2.isShorthandPropertyAssignment(prop)) {
7819
+ names.push(prop.getName());
7820
+ }
7821
+ }
7822
+ return names;
7823
+ }
7824
+ enclosingFunctionName(node) {
7825
+ let current = node.getParent();
7826
+ while (current) {
7827
+ const name = this.namedScope(current);
7828
+ if (name !== null)
7829
+ return name;
7830
+ current = current.getParent();
7831
+ }
7832
+ return "<module>";
7833
+ }
7834
+ namedScope(node) {
7835
+ if (Node2.isFunctionDeclaration(node)) {
7836
+ return node.getName() ?? "<anonymous function>";
7837
+ }
7838
+ if (Node2.isMethodDeclaration(node)) {
7839
+ const clsName = node.getFirstAncestorByKind(SyntaxKind.ClassDeclaration)?.getName();
7840
+ const methodName = node.getName();
7841
+ return clsName ? `${clsName}.${methodName}` : methodName;
7842
+ }
7843
+ if (Node2.isGetAccessorDeclaration(node) || Node2.isSetAccessorDeclaration(node)) {
7844
+ return node.getName();
7845
+ }
7846
+ if (Node2.isArrowFunction(node) || Node2.isFunctionExpression(node)) {
7847
+ return this.boundFunctionName(node) ?? null;
7848
+ }
7849
+ return null;
7850
+ }
7851
+ boundFunctionName(fn) {
7852
+ const parent = fn.getParent();
7853
+ if (!parent)
7854
+ return;
7855
+ if (Node2.isVariableDeclaration(parent))
7856
+ return parent.getName();
7857
+ if (Node2.isPropertyAssignment(parent))
7858
+ return parent.getName();
7859
+ return;
7860
+ }
7595
7861
  functionNameToMessageType(funcName) {
7596
7862
  let name = funcName.replace(/^handle/, "").replace(/^on/, "").replace(/^set/, "Set").replace(/^update/, "Update").replace(/^do/, "");
7597
7863
  if (name.length > 0) {
@@ -7756,7 +8022,8 @@ class TypeExtractor {
7756
8022
  globalStateConstraints: handlerAnalysis.globalStateConstraints,
7757
8023
  verifiedStates: handlerAnalysis.verifiedStates,
7758
8024
  meshOrPeerSignals: handlerAnalysis.meshOrPeerSignals,
7759
- resources: handlerAnalysis.resources
8025
+ resources: handlerAnalysis.resources,
8026
+ offSurfaceMutations: handlerAnalysis.offSurfaceMutations
7760
8027
  };
7761
8028
  }
7762
8029
  extractHandlerAnalysis() {
@@ -8227,7 +8494,7 @@ async function setupCommand() {
8227
8494
  displayAnalysisResults(analysis);
8228
8495
  displayAnalysisSummary(analysis);
8229
8496
  const configContent = generateConfig(analysis);
8230
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8497
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8231
8498
  writeConfigFile(configPath, configContent);
8232
8499
  displaySetupSuccess(configPath);
8233
8500
  } catch (_error) {
@@ -8275,7 +8542,7 @@ function getFieldStatus(confidence) {
8275
8542
  return color("⚠ Manual config", COLORS.red);
8276
8543
  }
8277
8544
  function writeConfigFile(configPath, configContent) {
8278
- const configDir = path4.dirname(configPath);
8545
+ const configDir = path5.dirname(configPath);
8279
8546
  if (!fs4.existsSync(configDir)) {
8280
8547
  fs4.mkdirSync(configDir, { recursive: true });
8281
8548
  }
@@ -8298,7 +8565,7 @@ function displaySetupSuccess(configPath) {
8298
8565
  console.log();
8299
8566
  }
8300
8567
  async function validateCommand() {
8301
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8568
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8302
8569
  console.log(color(`
8303
8570
  \uD83D\uDD0D Validating configuration...
8304
8571
  `, COLORS.blue));
@@ -8319,7 +8586,7 @@ async function validateCommand() {
8319
8586
  process.exit(1);
8320
8587
  }
8321
8588
  async function estimateCommand() {
8322
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8589
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8323
8590
  console.log(color(`
8324
8591
  \uD83D\uDCCA Estimating state space...
8325
8592
  `, COLORS.blue));
@@ -8462,8 +8729,8 @@ function isWitnessMode() {
8462
8729
  return process.argv.includes("--witness");
8463
8730
  }
8464
8731
  function displayModelCoverage(report) {
8465
- const { unwrittenFields, unconstrainedMutators, fieldCoverage } = report;
8466
- if (unwrittenFields.length === 0 && unconstrainedMutators.length === 0) {
8732
+ const { unwrittenFields, unconstrainedMutators, fieldCoverage, offSurfaceMutations } = report;
8733
+ if (unwrittenFields.length === 0 && unconstrainedMutators.length === 0 && offSurfaceMutations.length === 0) {
8467
8734
  console.log(color(`✓ Model coverage: all ${fieldCoverage.length} declared field(s) written by a modelled handler`, COLORS.green));
8468
8735
  console.log();
8469
8736
  return;
@@ -8487,11 +8754,24 @@ function displayModelCoverage(report) {
8487
8754
  console.log(color(" The checker explores these transitions but asserts nothing about their effect.", COLORS.gray));
8488
8755
  console.log();
8489
8756
  }
8757
+ if (offSurfaceMutations.length > 0) {
8758
+ console.log(color(`
8759
+ ⚠️ ${offSurfaceMutations.length} declared state field write(s) outside any modelled transition (polly#163):`, COLORS.yellow));
8760
+ for (const m of offSurfaceMutations) {
8761
+ console.log(color(` • ${m.field} mutated in ${m.function}() — ${m.file}:${m.line}`, COLORS.yellow));
8762
+ }
8763
+ console.log(color(" A non-dispatched path (a method, a non-exported function, a closure) writes", COLORS.gray));
8764
+ console.log(color(" verified state the checker never explores — the register() shape (#160). Even", COLORS.gray));
8765
+ console.log(color(" when a handler also writes the field, model coverage cannot see this writer.", COLORS.gray));
8766
+ console.log(color(" Route the change through a dispatched handler, or drop the field from the", COLORS.gray));
8767
+ console.log(color(" verified surface. See tools/verify/OFF-SURFACE-MUTATORS.md.", COLORS.gray));
8768
+ console.log();
8769
+ }
8490
8770
  }
8491
8771
  async function runModelCoverage(typedConfig, typedAnalysis, meshFindingCount) {
8492
8772
  const { computeModelCoverage: computeModelCoverage2, strictCoverageReasons: strictCoverageReasons2 } = await Promise.resolve().then(() => exports_model_coverage);
8493
8773
  const stateFields = Object.keys(typedConfig.state ?? {});
8494
- const coverage = computeModelCoverage2(stateFields, typedAnalysis.handlers);
8774
+ const coverage = computeModelCoverage2(stateFields, typedAnalysis.handlers, typedAnalysis.offSurfaceMutations ?? []);
8495
8775
  displayModelCoverage(coverage);
8496
8776
  if (!isStrictMode())
8497
8777
  return;
@@ -8513,7 +8793,7 @@ async function runModelCoverage(typedConfig, typedAnalysis, meshFindingCount) {
8513
8793
  process.exit(1);
8514
8794
  }
8515
8795
  async function verifyCommand() {
8516
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8796
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8517
8797
  console.log(color(`
8518
8798
  \uD83D\uDD0D Running verification...
8519
8799
  `, COLORS.blue));
@@ -8579,6 +8859,12 @@ function getMaxDepth(config) {
8579
8859
  }
8580
8860
  return;
8581
8861
  }
8862
+ function getCustomTLAPaths(config) {
8863
+ if ("messages" in config && config.customTLAPaths) {
8864
+ return config.customTLAPaths;
8865
+ }
8866
+ return {};
8867
+ }
8582
8868
  async function runCoupledFieldsLint(config, analysis) {
8583
8869
  const groups = config.coupledFields ?? [];
8584
8870
  if (groups.length === 0)
@@ -8726,18 +9012,23 @@ async function runSubsystemVerification(config, analysis) {
8726
9012
  const maxDepth = getMaxDepth(config);
8727
9013
  const { generateSubsystemTLA: generateSubsystemTLA2 } = await Promise.resolve().then(() => (init_tla(), exports_tla));
8728
9014
  const results = [];
9015
+ const customTLAPaths = getCustomTLAPaths(config);
8729
9016
  for (const name of subsystemNames) {
8730
9017
  const sub = subsystems[name];
8731
9018
  const startTime = Date.now();
9019
+ if (customTLAPaths[name]) {
9020
+ console.log(color(`⏭ ${name}: hand-written spec (customTLAPaths) — witnessed, not generated`, COLORS.gray));
9021
+ continue;
9022
+ }
8732
9023
  console.log(color(`⚙️ Verifying subsystem: ${name}...`, COLORS.blue));
8733
9024
  const { spec, cfg } = await generateSubsystemTLA2(name, sub, config, analysis);
8734
9025
  const ensuresCount = (spec.match(/^EnsuresAfter_\w+ ==/gm) ?? []).length;
8735
- const specDir = path4.join(process.cwd(), "specs", "tla", "generated", name);
9026
+ const specDir = path5.join(process.cwd(), "specs", "tla", "generated", name);
8736
9027
  if (!fs4.existsSync(specDir)) {
8737
9028
  fs4.mkdirSync(specDir, { recursive: true });
8738
9029
  }
8739
- const specPath = path4.join(specDir, `UserApp_${name}.tla`);
8740
- const cfgPath = path4.join(specDir, `UserApp_${name}.cfg`);
9030
+ const specPath = path5.join(specDir, `UserApp_${name}.tla`);
9031
+ const cfgPath = path5.join(specDir, `UserApp_${name}.cfg`);
8741
9032
  fs4.writeFileSync(specPath, spec);
8742
9033
  fs4.writeFileSync(cfgPath, cfg);
8743
9034
  findAndCopyBaseSpec(specDir);
@@ -8766,7 +9057,7 @@ async function runSubsystemVerification(config, analysis) {
8766
9057
  } else if (result.error) {
8767
9058
  console.log(color(` Error: ${result.error}`, COLORS.red));
8768
9059
  }
8769
- fs4.writeFileSync(path4.join(specDir, "tlc-output.log"), result.output);
9060
+ fs4.writeFileSync(path5.join(specDir, "tlc-output.log"), result.output);
8770
9061
  }
8771
9062
  }
8772
9063
  console.log();
@@ -8798,15 +9089,19 @@ async function runWitnessVerification(config) {
8798
9089
  }
8799
9090
  const { extractWitnesses: extractWitnesses2 } = await Promise.resolve().then(() => (init_witness(), exports_witness));
8800
9091
  const {
9092
+ bareFieldRenderer: bareFieldRenderer2,
8801
9093
  bddPredicateToTLA: bddPredicateToTLA2,
8802
9094
  buildWitnessCfg: buildWitnessCfg2,
8803
9095
  buildWitnessModule: buildWitnessModule2,
9096
+ parseModuleName: parseModuleName2,
8804
9097
  routeWitness: routeWitness2,
8805
9098
  witnessPolarity: witnessPolarity2,
9099
+ witnessSpecLocation: witnessSpecLocation2,
8806
9100
  witnessVerdict: witnessVerdict2,
8807
9101
  WITNESS_INVARIANT: WITNESS_INVARIANT2
8808
9102
  } = await Promise.resolve().then(() => (init_witness2(), exports_witness2));
8809
9103
  const subsystems = config.subsystems;
9104
+ const customTLAPaths = getCustomTLAPaths(config);
8810
9105
  const witnesses = await extractWitnesses2(featureFiles, stepFiles);
8811
9106
  const docker = await setupDocker();
8812
9107
  const timeoutSeconds = getTimeout(config);
@@ -8836,21 +9131,49 @@ async function runWitnessVerification(config) {
8836
9131
  });
8837
9132
  continue;
8838
9133
  }
8839
- const specDir = path4.join(cwd, "specs", "tla", "generated", subsystem);
8840
- const subsystemCfg = path4.join(specDir, `UserApp_${subsystem}.cfg`);
8841
- if (!fs4.existsSync(path4.join(specDir, `UserApp_${subsystem}.tla`)) || !fs4.existsSync(subsystemCfg)) {
8842
- results.push({
8843
- id,
8844
- status: "error",
8845
- ok: false,
8846
- subsystem,
8847
- note: `subsystem spec missing: ${subsystem}`
8848
- });
8849
- continue;
9134
+ const custom = customTLAPaths[subsystem];
9135
+ let location;
9136
+ if (custom) {
9137
+ const tlaAbs = path5.resolve(cwd, custom.tla);
9138
+ const cfgAbs = path5.resolve(cwd, custom.cfg);
9139
+ if (!fs4.existsSync(tlaAbs) || !fs4.existsSync(cfgAbs)) {
9140
+ results.push({
9141
+ id,
9142
+ status: "error",
9143
+ ok: false,
9144
+ subsystem,
9145
+ note: `custom spec missing: ${custom.tla} / ${custom.cfg}`
9146
+ });
9147
+ continue;
9148
+ }
9149
+ const module = custom.module ?? parseModuleName2(fs4.readFileSync(tlaAbs, "utf8"));
9150
+ if (!module) {
9151
+ results.push({
9152
+ id,
9153
+ status: "error",
9154
+ ok: false,
9155
+ subsystem,
9156
+ note: `cannot read MODULE name from ${custom.tla}; set customTLAPaths.${subsystem}.module`
9157
+ });
9158
+ continue;
9159
+ }
9160
+ location = witnessSpecLocation2(cwd, subsystem, custom, module);
9161
+ } else {
9162
+ location = witnessSpecLocation2(cwd, subsystem, undefined, "");
9163
+ if (!fs4.existsSync(location.tlaPath) || !fs4.existsSync(location.cfgPath)) {
9164
+ results.push({
9165
+ id,
9166
+ status: "error",
9167
+ ok: false,
9168
+ subsystem,
9169
+ note: `subsystem spec missing: ${subsystem}`
9170
+ });
9171
+ continue;
9172
+ }
8850
9173
  }
8851
9174
  let tlaPredicate;
8852
9175
  try {
8853
- tlaPredicate = bddPredicateToTLA2(w.predicate);
9176
+ tlaPredicate = location.custom ? bddPredicateToTLA2(w.predicate, bareFieldRenderer2(custom?.fields)) : bddPredicateToTLA2(w.predicate);
8854
9177
  } catch (err) {
8855
9178
  results.push({
8856
9179
  id,
@@ -8862,9 +9185,9 @@ async function runWitnessVerification(config) {
8862
9185
  continue;
8863
9186
  }
8864
9187
  const moduleName = `Witness_${subsystem}_${idx++}`;
8865
- const witnessTla = path4.join(specDir, `${moduleName}.tla`);
8866
- fs4.writeFileSync(witnessTla, buildWitnessModule2(moduleName, `UserApp_${subsystem}`, tlaPredicate));
8867
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.cfg`), buildWitnessCfg2(fs4.readFileSync(subsystemCfg, "utf8")));
9188
+ const witnessTla = path5.join(location.dir, `${moduleName}.tla`);
9189
+ fs4.writeFileSync(witnessTla, buildWitnessModule2(moduleName, location.module, tlaPredicate, { bare: location.custom }));
9190
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.cfg`), buildWitnessCfg2(fs4.readFileSync(location.cfgPath, "utf8")));
8868
9191
  const polarityTag = polarity === "forbidden" ? color(" [forbidden — must be unreachable]", COLORS.gray) : "";
8869
9192
  console.log(color(`⚙️ ${id}`, COLORS.blue) + polarityTag);
8870
9193
  console.log(color(` ${subsystem} ⊨ ${w.predicate}`, COLORS.gray));
@@ -8895,7 +9218,7 @@ async function runWitnessVerification(config) {
8895
9218
  note: tlc.error ?? tlc.violation?.name ?? "TLC error"
8896
9219
  });
8897
9220
  console.log(color(` ! error — ${tlc.error ?? "see log"}`, COLORS.yellow));
8898
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.tlc-output.log`), tlc.output);
9221
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.tlc-output.log`), tlc.output);
8899
9222
  continue;
8900
9223
  }
8901
9224
  const verdict = witnessVerdict2(polarity, reachable);
@@ -8910,7 +9233,7 @@ async function runWitnessVerification(config) {
8910
9233
  });
8911
9234
  console.log(color(` ${verdict.ok ? "✓" : "✗"} ${verdict.status} — ${verdict.message}`, verdict.ok ? COLORS.green : COLORS.red));
8912
9235
  if (!verdict.ok)
8913
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.tlc-output.log`), tlc.output);
9236
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.tlc-output.log`), tlc.output);
8914
9237
  }
8915
9238
  displayWitnessReport(results);
8916
9239
  }
@@ -9002,7 +9325,7 @@ function displayCompositionalReport(results, nonInterferenceValid) {
9002
9325
  console.log();
9003
9326
  }
9004
9327
  async function loadVerificationConfig(configPath) {
9005
- const resolvedPath = path4.resolve(configPath);
9328
+ const resolvedPath = path5.resolve(configPath);
9006
9329
  const configModule = await import(`file://${resolvedPath}?t=${Date.now()}`);
9007
9330
  return configModule.verificationConfig || configModule.default;
9008
9331
  }
@@ -9024,23 +9347,23 @@ async function generateAndWriteTLASpecs(config, analysis) {
9024
9347
  const { generateTLA: generateTLA2 } = await Promise.resolve().then(() => (init_tla(), exports_tla));
9025
9348
  console.log(color("\uD83D\uDCDD Generating TLA+ specification...", COLORS.blue));
9026
9349
  const { spec, cfg } = await generateTLA2(config, analysis);
9027
- const specDir = path4.join(process.cwd(), "specs", "tla", "generated");
9350
+ const specDir = path5.join(process.cwd(), "specs", "tla", "generated");
9028
9351
  if (!fs4.existsSync(specDir)) {
9029
9352
  fs4.mkdirSync(specDir, { recursive: true });
9030
9353
  }
9031
- const specPath = path4.join(specDir, "UserApp.tla");
9032
- const cfgPath = path4.join(specDir, "UserApp.cfg");
9354
+ const specPath = path5.join(specDir, "UserApp.tla");
9355
+ const cfgPath = path5.join(specDir, "UserApp.cfg");
9033
9356
  fs4.writeFileSync(specPath, spec);
9034
9357
  fs4.writeFileSync(cfgPath, cfg);
9035
9358
  return { specPath, specDir };
9036
9359
  }
9037
9360
  function findAndCopyBaseSpec(specDir) {
9038
9361
  const possiblePaths = [
9039
- path4.join(process.cwd(), "specs", "tla", "MessageRouter.tla"),
9040
- path4.join(__dirname, "..", "specs", "tla", "MessageRouter.tla"),
9041
- path4.join(__dirname, "..", "..", "specs", "tla", "MessageRouter.tla"),
9042
- path4.join(process.cwd(), "external", "polly", "packages", "verify", "specs", "tla", "MessageRouter.tla"),
9043
- path4.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla", "MessageRouter.tla")
9362
+ path5.join(process.cwd(), "specs", "tla", "MessageRouter.tla"),
9363
+ path5.join(__dirname, "..", "specs", "tla", "MessageRouter.tla"),
9364
+ path5.join(__dirname, "..", "..", "specs", "tla", "MessageRouter.tla"),
9365
+ path5.join(process.cwd(), "external", "polly", "packages", "verify", "specs", "tla", "MessageRouter.tla"),
9366
+ path5.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla", "MessageRouter.tla")
9044
9367
  ];
9045
9368
  let baseSpecPath = null;
9046
9369
  for (const candidatePath of possiblePaths) {
@@ -9050,7 +9373,7 @@ function findAndCopyBaseSpec(specDir) {
9050
9373
  }
9051
9374
  }
9052
9375
  if (baseSpecPath) {
9053
- const destSpecPath = path4.join(specDir, "MessageRouter.tla");
9376
+ const destSpecPath = path5.join(specDir, "MessageRouter.tla");
9054
9377
  fs4.copyFileSync(baseSpecPath, destSpecPath);
9055
9378
  console.log(color("✓ Copied MessageRouter.tla", COLORS.green));
9056
9379
  } else {
@@ -9063,13 +9386,13 @@ function findAndCopyBaseSpec(specDir) {
9063
9386
  }
9064
9387
  function findMeshSeedSpecDir() {
9065
9388
  const candidates = [
9066
- path4.join(process.cwd(), "specs", "tla"),
9067
- path4.join(__dirname, "..", "specs", "tla"),
9068
- path4.join(__dirname, "..", "..", "specs", "tla"),
9069
- path4.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla")
9389
+ path5.join(process.cwd(), "specs", "tla"),
9390
+ path5.join(__dirname, "..", "specs", "tla"),
9391
+ path5.join(__dirname, "..", "..", "specs", "tla"),
9392
+ path5.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla")
9070
9393
  ];
9071
9394
  for (const dir of candidates) {
9072
- if (fs4.existsSync(path4.join(dir, "MeshSeed.tla")))
9395
+ if (fs4.existsSync(path5.join(dir, "MeshSeed.tla")))
9073
9396
  return dir;
9074
9397
  }
9075
9398
  return null;
@@ -9083,11 +9406,11 @@ async function runMeshSeedGuard(docker, specDir, config) {
9083
9406
  return;
9084
9407
  }
9085
9408
  const fixDisabled = isSeedFixDisabled();
9086
- fs4.copyFileSync(path4.join(sourceDir, "MeshSeed.tla"), path4.join(specDir, "MeshSeed.tla"));
9087
- const baseCfg = fs4.readFileSync(path4.join(sourceDir, "MeshSeed.cfg"), "utf8");
9088
- fs4.writeFileSync(path4.join(specDir, "MeshSeed.cfg"), meshSeedCfg(baseCfg, { disableFix: fixDisabled }));
9409
+ fs4.copyFileSync(path5.join(sourceDir, "MeshSeed.tla"), path5.join(specDir, "MeshSeed.tla"));
9410
+ const baseCfg = fs4.readFileSync(path5.join(sourceDir, "MeshSeed.cfg"), "utf8");
9411
+ fs4.writeFileSync(path5.join(specDir, "MeshSeed.cfg"), meshSeedCfg(baseCfg, { disableFix: fixDisabled }));
9089
9412
  console.log(color(`⚙️ Running mesh seed-race guard (MeshSeed.tla, SeedDeterministic = ${fixDisabled ? "FALSE" : "TRUE"})...`, COLORS.blue));
9090
- return docker.runTLC(path4.join(specDir, "MeshSeed.tla"), { workers: 1 });
9413
+ return docker.runTLC(path5.join(specDir, "MeshSeed.tla"), { workers: 1 });
9091
9414
  }
9092
9415
  async function setupDocker() {
9093
9416
  const { DockerRunner: DockerRunner2 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
@@ -9169,8 +9492,8 @@ function displayVerificationResults(result, specDir) {
9169
9492
  }
9170
9493
  console.log();
9171
9494
  console.log(color("Full output saved to:", COLORS.gray));
9172
- console.log(color(` ${path4.join(specDir, "tlc-output.log")}`, COLORS.gray));
9173
- fs4.writeFileSync(path4.join(specDir, "tlc-output.log"), result.output);
9495
+ console.log(color(` ${path5.join(specDir, "tlc-output.log")}`, COLORS.gray));
9496
+ fs4.writeFileSync(path5.join(specDir, "tlc-output.log"), result.output);
9174
9497
  process.exit(1);
9175
9498
  }
9176
9499
  function showHelp() {
@@ -9233,8 +9556,8 @@ ${color("Learn More:", COLORS.blue)}
9233
9556
  }
9234
9557
  function findTsConfig() {
9235
9558
  const locations = [
9236
- path4.join(process.cwd(), "tsconfig.json"),
9237
- path4.join(process.cwd(), "packages", "web-ext", "tsconfig.json")
9559
+ path5.join(process.cwd(), "tsconfig.json"),
9560
+ path5.join(process.cwd(), "packages", "web-ext", "tsconfig.json")
9238
9561
  ];
9239
9562
  for (const loc of locations) {
9240
9563
  if (fs4.existsSync(loc)) {
@@ -9245,9 +9568,9 @@ function findTsConfig() {
9245
9568
  }
9246
9569
  function findStateFile() {
9247
9570
  const locations = [
9248
- path4.join(process.cwd(), "types", "state.ts"),
9249
- path4.join(process.cwd(), "src", "types", "state.ts"),
9250
- path4.join(process.cwd(), "packages", "web-ext", "src", "shared", "state", "app-state.ts")
9571
+ path5.join(process.cwd(), "types", "state.ts"),
9572
+ path5.join(process.cwd(), "src", "types", "state.ts"),
9573
+ path5.join(process.cwd(), "packages", "web-ext", "src", "shared", "state", "app-state.ts")
9251
9574
  ];
9252
9575
  for (const loc of locations) {
9253
9576
  if (fs4.existsSync(loc)) {
@@ -9261,4 +9584,4 @@ main().catch((error) => {
9261
9584
  process.exit(1);
9262
9585
  });
9263
9586
 
9264
- //# debugId=F9E0214A7A9A110B64756E2164756E21
9587
+ //# debugId=F55B4EE81F3A37C864756E2164756E21