@fairfox/polly 0.84.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
  }
@@ -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;
@@ -3467,11 +3537,22 @@ function headerLine(cfg, header) {
3467
3537
  return null;
3468
3538
  }
3469
3539
  function buildWitnessCfg(baseCfg) {
3470
- 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"];
3471
3544
  const constants = sectionBody(baseCfg, "CONSTANTS");
3472
3545
  const constraint = sectionBody(baseCfg, "CONSTRAINT");
3473
3546
  const symmetry = sectionBody(baseCfg, "SYMMETRY");
3474
- 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
+ ];
3475
3556
  if (constraint.length > 0)
3476
3557
  out.push("", "CONSTRAINT", ...constraint);
3477
3558
  if (symmetry.length > 0)
@@ -3490,7 +3571,7 @@ function routeWitness(fields, subsystems) {
3490
3571
  const only = owners.length === 1 ? owners[0] : undefined;
3491
3572
  return only ? only[0] : null;
3492
3573
  }
3493
- var WITNESS_INVARIANT = "WitnessReachable", WitnessTranslationError, COMPARATORS;
3574
+ var WITNESS_INVARIANT = "WitnessReachable", WitnessTranslationError, COMPARATORS, contextFieldRenderer = (fieldPath) => `contextStates[ctx].${flattenField(fieldPath)}`;
3494
3575
  var init_witness2 = __esm(() => {
3495
3576
  WitnessTranslationError = class WitnessTranslationError extends Error {
3496
3577
  };
@@ -3513,11 +3594,11 @@ __export(exports_docker, {
3513
3594
  });
3514
3595
  import { spawn } from "node:child_process";
3515
3596
  import * as fs3 from "node:fs";
3516
- import * as path3 from "node:path";
3597
+ import * as path4 from "node:path";
3517
3598
 
3518
3599
  class DockerRunner {
3519
3600
  IMAGE_NAME = "polly-tla:latest";
3520
- DOCKERFILE_PATH = path3.join(__dirname, "../../Dockerfile");
3601
+ DOCKERFILE_PATH = path4.join(__dirname, "../../Dockerfile");
3521
3602
  async isDockerAvailable() {
3522
3603
  try {
3523
3604
  const result = await this.runCommand("docker", ["info"], {
@@ -3545,7 +3626,7 @@ class DockerRunner {
3545
3626
  }
3546
3627
  }
3547
3628
  async buildImage(onProgress) {
3548
- const dockerfileDir = path3.dirname(this.DOCKERFILE_PATH);
3629
+ const dockerfileDir = path4.dirname(this.DOCKERFILE_PATH);
3549
3630
  await this.runCommandStreaming("docker", ["build", "-f", this.DOCKERFILE_PATH, "-t", this.IMAGE_NAME, dockerfileDir], onProgress, 300000);
3550
3631
  }
3551
3632
  async pullImage(onProgress) {
@@ -3555,13 +3636,13 @@ class DockerRunner {
3555
3636
  if (!fs3.existsSync(specPath)) {
3556
3637
  throw new Error(`Spec file not found: ${specPath}`);
3557
3638
  }
3558
- const specDir = path3.dirname(specPath);
3559
- const specName = path3.basename(specPath, ".tla");
3560
- 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`);
3561
3642
  if (!fs3.existsSync(cfgPath)) {
3562
3643
  throw new Error(`Config file not found: ${cfgPath}`);
3563
3644
  }
3564
- const statesDir = path3.join(specDir, "states");
3645
+ const statesDir = path4.join(specDir, "states");
3565
3646
  if (fs3.existsSync(statesDir)) {
3566
3647
  fs3.rmSync(statesDir, { recursive: true, force: true });
3567
3648
  }
@@ -3654,7 +3735,7 @@ class DockerRunner {
3654
3735
  return "Unknown error occurred during model checking";
3655
3736
  }
3656
3737
  runCommand(command, args, options) {
3657
- return new Promise((resolve2, reject) => {
3738
+ return new Promise((resolve3, reject) => {
3658
3739
  const proc = spawn(command, args);
3659
3740
  let stdout = "";
3660
3741
  let stderr = "";
@@ -3672,7 +3753,7 @@ class DockerRunner {
3672
3753
  proc.on("close", (exitCode) => {
3673
3754
  if (timeout)
3674
3755
  clearTimeout(timeout);
3675
- resolve2({
3756
+ resolve3({
3676
3757
  exitCode: exitCode || 0,
3677
3758
  stdout,
3678
3759
  stderr
@@ -3686,7 +3767,7 @@ class DockerRunner {
3686
3767
  });
3687
3768
  }
3688
3769
  runCommandStreaming(command, args, onOutput, timeout) {
3689
- return new Promise((resolve2, reject) => {
3770
+ return new Promise((resolve3, reject) => {
3690
3771
  const proc = spawn(command, args);
3691
3772
  const timeoutHandle = timeout && timeout > 0 ? setTimeout(() => {
3692
3773
  proc.kill();
@@ -3718,7 +3799,7 @@ class DockerRunner {
3718
3799
  if (timeoutHandle)
3719
3800
  clearTimeout(timeoutHandle);
3720
3801
  if (exitCode === 0) {
3721
- resolve2();
3802
+ resolve3();
3722
3803
  } else {
3723
3804
  reject(new Error(`Command failed with exit code ${exitCode}`));
3724
3805
  }
@@ -3737,7 +3818,7 @@ var init_docker = () => {};
3737
3818
  // tools/verify/src/cli.ts
3738
3819
  init_expression_validator();
3739
3820
  import * as fs4 from "node:fs";
3740
- import * as path4 from "node:path";
3821
+ import * as path5 from "node:path";
3741
3822
 
3742
3823
  // tools/verify/src/analysis/mesh-signal-warnings.ts
3743
3824
  function computeMeshOrPeerSignalFindings(analysis, declaredMeshDocs) {
@@ -5464,6 +5545,7 @@ class HandlerExtractor {
5464
5545
  warnings;
5465
5546
  currentFunctionParams = [];
5466
5547
  contextOverrides;
5548
+ onSurfaceSpans = [];
5467
5549
  constructor(tsConfigPath, contextOverrides) {
5468
5550
  this.project = new Project({
5469
5551
  tsConfigFilePath: tsConfigPath
@@ -5523,6 +5605,7 @@ class HandlerExtractor {
5523
5605
  const meshOrPeerSignals = [];
5524
5606
  const resources = [];
5525
5607
  this.warnings = [];
5608
+ this.onSurfaceSpans = [];
5526
5609
  const allSourceFiles = this.project.getSourceFiles();
5527
5610
  const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
5528
5611
  this.debugLogSourceFiles(allSourceFiles, entryPoints);
@@ -5557,6 +5640,30 @@ class HandlerExtractor {
5557
5640
  continue;
5558
5641
  meshOrPeerSignals.push(...this.extractMeshOrPeerSignalsFromFile(sourceFile));
5559
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
+ }
5560
5667
  this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
5561
5668
  this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
5562
5669
  return {
@@ -5567,6 +5674,7 @@ class HandlerExtractor {
5567
5674
  verifiedStates,
5568
5675
  meshOrPeerSignals,
5569
5676
  resources,
5677
+ offSurfaceMutations,
5570
5678
  warnings: this.warnings
5571
5679
  };
5572
5680
  }
@@ -5977,6 +6085,7 @@ class HandlerExtractor {
5977
6085
  return false;
5978
6086
  }
5979
6087
  extractAssignments(funcNode, assignments) {
6088
+ this.recordOnSurfaceSpan(funcNode);
5980
6089
  funcNode.forEachDescendant((node) => {
5981
6090
  if (Node2.isBinaryExpression(node)) {
5982
6091
  this.extractBinaryExpressionAssignment(node, assignments);
@@ -7563,6 +7672,7 @@ class HandlerExtractor {
7563
7672
  }
7564
7673
  findStateMutationsInFunction(func, stateVarNames) {
7565
7674
  const mutations = [];
7675
+ this.recordOnSurfaceSpan(func);
7566
7676
  func.forEachDescendant((node) => {
7567
7677
  if (!Node2.isBinaryExpression(node))
7568
7678
  return;
@@ -7592,6 +7702,140 @@ class HandlerExtractor {
7592
7702
  });
7593
7703
  return mutations;
7594
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
+ }
7595
7839
  functionNameToMessageType(funcName) {
7596
7840
  let name = funcName.replace(/^handle/, "").replace(/^on/, "").replace(/^set/, "Set").replace(/^update/, "Update").replace(/^do/, "");
7597
7841
  if (name.length > 0) {
@@ -7756,7 +8000,8 @@ class TypeExtractor {
7756
8000
  globalStateConstraints: handlerAnalysis.globalStateConstraints,
7757
8001
  verifiedStates: handlerAnalysis.verifiedStates,
7758
8002
  meshOrPeerSignals: handlerAnalysis.meshOrPeerSignals,
7759
- resources: handlerAnalysis.resources
8003
+ resources: handlerAnalysis.resources,
8004
+ offSurfaceMutations: handlerAnalysis.offSurfaceMutations
7760
8005
  };
7761
8006
  }
7762
8007
  extractHandlerAnalysis() {
@@ -8227,7 +8472,7 @@ async function setupCommand() {
8227
8472
  displayAnalysisResults(analysis);
8228
8473
  displayAnalysisSummary(analysis);
8229
8474
  const configContent = generateConfig(analysis);
8230
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8475
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8231
8476
  writeConfigFile(configPath, configContent);
8232
8477
  displaySetupSuccess(configPath);
8233
8478
  } catch (_error) {
@@ -8275,7 +8520,7 @@ function getFieldStatus(confidence) {
8275
8520
  return color("⚠ Manual config", COLORS.red);
8276
8521
  }
8277
8522
  function writeConfigFile(configPath, configContent) {
8278
- const configDir = path4.dirname(configPath);
8523
+ const configDir = path5.dirname(configPath);
8279
8524
  if (!fs4.existsSync(configDir)) {
8280
8525
  fs4.mkdirSync(configDir, { recursive: true });
8281
8526
  }
@@ -8298,7 +8543,7 @@ function displaySetupSuccess(configPath) {
8298
8543
  console.log();
8299
8544
  }
8300
8545
  async function validateCommand() {
8301
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8546
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8302
8547
  console.log(color(`
8303
8548
  \uD83D\uDD0D Validating configuration...
8304
8549
  `, COLORS.blue));
@@ -8319,7 +8564,7 @@ async function validateCommand() {
8319
8564
  process.exit(1);
8320
8565
  }
8321
8566
  async function estimateCommand() {
8322
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8567
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8323
8568
  console.log(color(`
8324
8569
  \uD83D\uDCCA Estimating state space...
8325
8570
  `, COLORS.blue));
@@ -8462,8 +8707,8 @@ function isWitnessMode() {
8462
8707
  return process.argv.includes("--witness");
8463
8708
  }
8464
8709
  function displayModelCoverage(report) {
8465
- const { unwrittenFields, unconstrainedMutators, fieldCoverage } = report;
8466
- 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) {
8467
8712
  console.log(color(`✓ Model coverage: all ${fieldCoverage.length} declared field(s) written by a modelled handler`, COLORS.green));
8468
8713
  console.log();
8469
8714
  return;
@@ -8487,11 +8732,24 @@ function displayModelCoverage(report) {
8487
8732
  console.log(color(" The checker explores these transitions but asserts nothing about their effect.", COLORS.gray));
8488
8733
  console.log();
8489
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
+ }
8490
8748
  }
8491
8749
  async function runModelCoverage(typedConfig, typedAnalysis, meshFindingCount) {
8492
8750
  const { computeModelCoverage: computeModelCoverage2, strictCoverageReasons: strictCoverageReasons2 } = await Promise.resolve().then(() => exports_model_coverage);
8493
8751
  const stateFields = Object.keys(typedConfig.state ?? {});
8494
- const coverage = computeModelCoverage2(stateFields, typedAnalysis.handlers);
8752
+ const coverage = computeModelCoverage2(stateFields, typedAnalysis.handlers, typedAnalysis.offSurfaceMutations ?? []);
8495
8753
  displayModelCoverage(coverage);
8496
8754
  if (!isStrictMode())
8497
8755
  return;
@@ -8513,7 +8771,7 @@ async function runModelCoverage(typedConfig, typedAnalysis, meshFindingCount) {
8513
8771
  process.exit(1);
8514
8772
  }
8515
8773
  async function verifyCommand() {
8516
- const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
8774
+ const configPath = path5.join(process.cwd(), "specs", "verification.config.ts");
8517
8775
  console.log(color(`
8518
8776
  \uD83D\uDD0D Running verification...
8519
8777
  `, COLORS.blue));
@@ -8579,6 +8837,12 @@ function getMaxDepth(config) {
8579
8837
  }
8580
8838
  return;
8581
8839
  }
8840
+ function getCustomTLAPaths(config) {
8841
+ if ("messages" in config && config.customTLAPaths) {
8842
+ return config.customTLAPaths;
8843
+ }
8844
+ return {};
8845
+ }
8582
8846
  async function runCoupledFieldsLint(config, analysis) {
8583
8847
  const groups = config.coupledFields ?? [];
8584
8848
  if (groups.length === 0)
@@ -8726,18 +8990,23 @@ async function runSubsystemVerification(config, analysis) {
8726
8990
  const maxDepth = getMaxDepth(config);
8727
8991
  const { generateSubsystemTLA: generateSubsystemTLA2 } = await Promise.resolve().then(() => (init_tla(), exports_tla));
8728
8992
  const results = [];
8993
+ const customTLAPaths = getCustomTLAPaths(config);
8729
8994
  for (const name of subsystemNames) {
8730
8995
  const sub = subsystems[name];
8731
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
+ }
8732
9001
  console.log(color(`⚙️ Verifying subsystem: ${name}...`, COLORS.blue));
8733
9002
  const { spec, cfg } = await generateSubsystemTLA2(name, sub, config, analysis);
8734
9003
  const ensuresCount = (spec.match(/^EnsuresAfter_\w+ ==/gm) ?? []).length;
8735
- const specDir = path4.join(process.cwd(), "specs", "tla", "generated", name);
9004
+ const specDir = path5.join(process.cwd(), "specs", "tla", "generated", name);
8736
9005
  if (!fs4.existsSync(specDir)) {
8737
9006
  fs4.mkdirSync(specDir, { recursive: true });
8738
9007
  }
8739
- const specPath = path4.join(specDir, `UserApp_${name}.tla`);
8740
- 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`);
8741
9010
  fs4.writeFileSync(specPath, spec);
8742
9011
  fs4.writeFileSync(cfgPath, cfg);
8743
9012
  findAndCopyBaseSpec(specDir);
@@ -8766,7 +9035,7 @@ async function runSubsystemVerification(config, analysis) {
8766
9035
  } else if (result.error) {
8767
9036
  console.log(color(` Error: ${result.error}`, COLORS.red));
8768
9037
  }
8769
- fs4.writeFileSync(path4.join(specDir, "tlc-output.log"), result.output);
9038
+ fs4.writeFileSync(path5.join(specDir, "tlc-output.log"), result.output);
8770
9039
  }
8771
9040
  }
8772
9041
  console.log();
@@ -8798,15 +9067,19 @@ async function runWitnessVerification(config) {
8798
9067
  }
8799
9068
  const { extractWitnesses: extractWitnesses2 } = await Promise.resolve().then(() => (init_witness(), exports_witness));
8800
9069
  const {
9070
+ bareFieldRenderer: bareFieldRenderer2,
8801
9071
  bddPredicateToTLA: bddPredicateToTLA2,
8802
9072
  buildWitnessCfg: buildWitnessCfg2,
8803
9073
  buildWitnessModule: buildWitnessModule2,
9074
+ parseModuleName: parseModuleName2,
8804
9075
  routeWitness: routeWitness2,
8805
9076
  witnessPolarity: witnessPolarity2,
9077
+ witnessSpecLocation: witnessSpecLocation2,
8806
9078
  witnessVerdict: witnessVerdict2,
8807
9079
  WITNESS_INVARIANT: WITNESS_INVARIANT2
8808
9080
  } = await Promise.resolve().then(() => (init_witness2(), exports_witness2));
8809
9081
  const subsystems = config.subsystems;
9082
+ const customTLAPaths = getCustomTLAPaths(config);
8810
9083
  const witnesses = await extractWitnesses2(featureFiles, stepFiles);
8811
9084
  const docker = await setupDocker();
8812
9085
  const timeoutSeconds = getTimeout(config);
@@ -8836,21 +9109,49 @@ async function runWitnessVerification(config) {
8836
9109
  });
8837
9110
  continue;
8838
9111
  }
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;
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
+ }
8850
9151
  }
8851
9152
  let tlaPredicate;
8852
9153
  try {
8853
- tlaPredicate = bddPredicateToTLA2(w.predicate);
9154
+ tlaPredicate = location.custom ? bddPredicateToTLA2(w.predicate, bareFieldRenderer2(custom?.fields)) : bddPredicateToTLA2(w.predicate);
8854
9155
  } catch (err) {
8855
9156
  results.push({
8856
9157
  id,
@@ -8862,9 +9163,9 @@ async function runWitnessVerification(config) {
8862
9163
  continue;
8863
9164
  }
8864
9165
  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")));
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")));
8868
9169
  const polarityTag = polarity === "forbidden" ? color(" [forbidden — must be unreachable]", COLORS.gray) : "";
8869
9170
  console.log(color(`⚙️ ${id}`, COLORS.blue) + polarityTag);
8870
9171
  console.log(color(` ${subsystem} ⊨ ${w.predicate}`, COLORS.gray));
@@ -8895,7 +9196,7 @@ async function runWitnessVerification(config) {
8895
9196
  note: tlc.error ?? tlc.violation?.name ?? "TLC error"
8896
9197
  });
8897
9198
  console.log(color(` ! error — ${tlc.error ?? "see log"}`, COLORS.yellow));
8898
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.tlc-output.log`), tlc.output);
9199
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.tlc-output.log`), tlc.output);
8899
9200
  continue;
8900
9201
  }
8901
9202
  const verdict = witnessVerdict2(polarity, reachable);
@@ -8910,7 +9211,7 @@ async function runWitnessVerification(config) {
8910
9211
  });
8911
9212
  console.log(color(` ${verdict.ok ? "✓" : "✗"} ${verdict.status} — ${verdict.message}`, verdict.ok ? COLORS.green : COLORS.red));
8912
9213
  if (!verdict.ok)
8913
- fs4.writeFileSync(path4.join(specDir, `${moduleName}.tlc-output.log`), tlc.output);
9214
+ fs4.writeFileSync(path5.join(location.dir, `${moduleName}.tlc-output.log`), tlc.output);
8914
9215
  }
8915
9216
  displayWitnessReport(results);
8916
9217
  }
@@ -9002,7 +9303,7 @@ function displayCompositionalReport(results, nonInterferenceValid) {
9002
9303
  console.log();
9003
9304
  }
9004
9305
  async function loadVerificationConfig(configPath) {
9005
- const resolvedPath = path4.resolve(configPath);
9306
+ const resolvedPath = path5.resolve(configPath);
9006
9307
  const configModule = await import(`file://${resolvedPath}?t=${Date.now()}`);
9007
9308
  return configModule.verificationConfig || configModule.default;
9008
9309
  }
@@ -9024,23 +9325,23 @@ async function generateAndWriteTLASpecs(config, analysis) {
9024
9325
  const { generateTLA: generateTLA2 } = await Promise.resolve().then(() => (init_tla(), exports_tla));
9025
9326
  console.log(color("\uD83D\uDCDD Generating TLA+ specification...", COLORS.blue));
9026
9327
  const { spec, cfg } = await generateTLA2(config, analysis);
9027
- const specDir = path4.join(process.cwd(), "specs", "tla", "generated");
9328
+ const specDir = path5.join(process.cwd(), "specs", "tla", "generated");
9028
9329
  if (!fs4.existsSync(specDir)) {
9029
9330
  fs4.mkdirSync(specDir, { recursive: true });
9030
9331
  }
9031
- const specPath = path4.join(specDir, "UserApp.tla");
9032
- const cfgPath = path4.join(specDir, "UserApp.cfg");
9332
+ const specPath = path5.join(specDir, "UserApp.tla");
9333
+ const cfgPath = path5.join(specDir, "UserApp.cfg");
9033
9334
  fs4.writeFileSync(specPath, spec);
9034
9335
  fs4.writeFileSync(cfgPath, cfg);
9035
9336
  return { specPath, specDir };
9036
9337
  }
9037
9338
  function findAndCopyBaseSpec(specDir) {
9038
9339
  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")
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")
9044
9345
  ];
9045
9346
  let baseSpecPath = null;
9046
9347
  for (const candidatePath of possiblePaths) {
@@ -9050,7 +9351,7 @@ function findAndCopyBaseSpec(specDir) {
9050
9351
  }
9051
9352
  }
9052
9353
  if (baseSpecPath) {
9053
- const destSpecPath = path4.join(specDir, "MessageRouter.tla");
9354
+ const destSpecPath = path5.join(specDir, "MessageRouter.tla");
9054
9355
  fs4.copyFileSync(baseSpecPath, destSpecPath);
9055
9356
  console.log(color("✓ Copied MessageRouter.tla", COLORS.green));
9056
9357
  } else {
@@ -9063,13 +9364,13 @@ function findAndCopyBaseSpec(specDir) {
9063
9364
  }
9064
9365
  function findMeshSeedSpecDir() {
9065
9366
  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")
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")
9070
9371
  ];
9071
9372
  for (const dir of candidates) {
9072
- if (fs4.existsSync(path4.join(dir, "MeshSeed.tla")))
9373
+ if (fs4.existsSync(path5.join(dir, "MeshSeed.tla")))
9073
9374
  return dir;
9074
9375
  }
9075
9376
  return null;
@@ -9083,11 +9384,11 @@ async function runMeshSeedGuard(docker, specDir, config) {
9083
9384
  return;
9084
9385
  }
9085
9386
  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 }));
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 }));
9089
9390
  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 });
9391
+ return docker.runTLC(path5.join(specDir, "MeshSeed.tla"), { workers: 1 });
9091
9392
  }
9092
9393
  async function setupDocker() {
9093
9394
  const { DockerRunner: DockerRunner2 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
@@ -9169,8 +9470,8 @@ function displayVerificationResults(result, specDir) {
9169
9470
  }
9170
9471
  console.log();
9171
9472
  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);
9473
+ console.log(color(` ${path5.join(specDir, "tlc-output.log")}`, COLORS.gray));
9474
+ fs4.writeFileSync(path5.join(specDir, "tlc-output.log"), result.output);
9174
9475
  process.exit(1);
9175
9476
  }
9176
9477
  function showHelp() {
@@ -9233,8 +9534,8 @@ ${color("Learn More:", COLORS.blue)}
9233
9534
  }
9234
9535
  function findTsConfig() {
9235
9536
  const locations = [
9236
- path4.join(process.cwd(), "tsconfig.json"),
9237
- 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")
9238
9539
  ];
9239
9540
  for (const loc of locations) {
9240
9541
  if (fs4.existsSync(loc)) {
@@ -9245,9 +9546,9 @@ function findTsConfig() {
9245
9546
  }
9246
9547
  function findStateFile() {
9247
9548
  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")
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")
9251
9552
  ];
9252
9553
  for (const loc of locations) {
9253
9554
  if (fs4.existsSync(loc)) {
@@ -9261,4 +9562,4 @@ main().catch((error) => {
9261
9562
  process.exit(1);
9262
9563
  });
9263
9564
 
9264
- //# debugId=F9E0214A7A9A110B64756E2164756E21
9565
+ //# debugId=0E6C5C2B308F1EA464756E2164756E21