@fairfox/polly 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/cli/polly.js +9 -9
  2. package/dist/cli/polly.js.map +4 -4
  3. package/dist/cli/template-utils.js +3 -3
  4. package/dist/cli/template-utils.js.map +3 -3
  5. package/dist/vendor/verify/src/cli.js +115 -72
  6. package/dist/vendor/verify/src/cli.js.map +10 -10
  7. package/dist/vendor/verify/src/public-api.d.ts +41 -0
  8. package/dist/vendor/verify/src/public-api.js +26 -0
  9. package/dist/vendor/verify/src/public-api.js.map +10 -0
  10. package/dist/vendor/visualize/src/cli.js +123 -105
  11. package/dist/vendor/visualize/src/cli.js.map +13 -13
  12. package/package.json +6 -4
  13. package/templates/pwa/build.ts.template +37 -37
  14. package/templates/pwa/server.ts.template +53 -53
  15. package/templates/pwa/src/service-worker.ts.template +131 -135
  16. package/templates/pwa/src/shared-worker.ts.template +114 -109
  17. /package/dist/{background → src/background}/api-client.d.ts +0 -0
  18. /package/dist/{background → src/background}/context-menu.d.ts +0 -0
  19. /package/dist/{background → src/background}/index.d.ts +0 -0
  20. /package/dist/{background → src/background}/log-store.d.ts +0 -0
  21. /package/dist/{background → src/background}/message-router.d.ts +0 -0
  22. /package/dist/{background → src/background}/offscreen-manager.d.ts +0 -0
  23. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  24. /package/dist/{shared → src/shared}/adapters/chrome/context-menus.chrome.d.ts +0 -0
  25. /package/dist/{shared → src/shared}/adapters/chrome/offscreen.chrome.d.ts +0 -0
  26. /package/dist/{shared → src/shared}/adapters/chrome/runtime.chrome.d.ts +0 -0
  27. /package/dist/{shared → src/shared}/adapters/chrome/storage.chrome.d.ts +0 -0
  28. /package/dist/{shared → src/shared}/adapters/chrome/tabs.chrome.d.ts +0 -0
  29. /package/dist/{shared → src/shared}/adapters/chrome/window.chrome.d.ts +0 -0
  30. /package/dist/{shared → src/shared}/adapters/context-menus.adapter.d.ts +0 -0
  31. /package/dist/{shared → src/shared}/adapters/fetch.adapter.d.ts +0 -0
  32. /package/dist/{shared → src/shared}/adapters/index.d.ts +0 -0
  33. /package/dist/{shared → src/shared}/adapters/logger.adapter.d.ts +0 -0
  34. /package/dist/{shared → src/shared}/adapters/offscreen.adapter.d.ts +0 -0
  35. /package/dist/{shared → src/shared}/adapters/runtime.adapter.d.ts +0 -0
  36. /package/dist/{shared → src/shared}/adapters/storage.adapter.d.ts +0 -0
  37. /package/dist/{shared → src/shared}/adapters/tabs.adapter.d.ts +0 -0
  38. /package/dist/{shared → src/shared}/adapters/window.adapter.d.ts +0 -0
  39. /package/dist/{shared → src/shared}/lib/context-helpers.d.ts +0 -0
  40. /package/dist/{shared → src/shared}/lib/context-specific-helpers.d.ts +0 -0
  41. /package/dist/{shared → src/shared}/lib/errors.d.ts +0 -0
  42. /package/dist/{shared → src/shared}/lib/handler-execution-tracker.d.ts +0 -0
  43. /package/dist/{shared → src/shared}/lib/message-bus.d.ts +0 -0
  44. /package/dist/{shared → src/shared}/lib/state.d.ts +0 -0
  45. /package/dist/{shared → src/shared}/lib/test-helpers.d.ts +0 -0
  46. /package/dist/{shared → src/shared}/state/app-state.d.ts +0 -0
  47. /package/dist/{shared → src/shared}/types/messages.d.ts +0 -0
@@ -27,7 +27,7 @@ class TLAGenerator {
27
27
  this.lines = [];
28
28
  this.indent = 0;
29
29
  const spec = this.generateSpec(config, analysis);
30
- const cfg = this.generateConfig(config, analysis);
30
+ const cfg = this.generateConfig(config);
31
31
  return { spec, cfg };
32
32
  }
33
33
  generateSpec(config, analysis) {
@@ -35,7 +35,7 @@ class TLAGenerator {
35
35
  this.indent = 0;
36
36
  this.addHeader();
37
37
  this.addExtends();
38
- this.addConstants(config, analysis);
38
+ this.addConstants(config);
39
39
  this.addMessageTypes(config, analysis);
40
40
  this.addStateType(config, analysis);
41
41
  this.addVariables();
@@ -48,7 +48,7 @@ class TLAGenerator {
48
48
  return this.lines.join(`
49
49
  `);
50
50
  }
51
- generateConfig(config, analysis) {
51
+ generateConfig(config) {
52
52
  const lines = [];
53
53
  lines.push("SPECIFICATION UserSpec");
54
54
  lines.push("");
@@ -107,8 +107,8 @@ class TLAGenerator {
107
107
  this.line("EXTENDS MessageRouter");
108
108
  this.line("");
109
109
  }
110
- addConstants(config, analysis) {
111
- const hasCustomConstants = Object.entries(config.state).some(([field, fieldConfig]) => {
110
+ addConstants(config) {
111
+ const hasCustomConstants = Object.entries(config.state).some(([_, fieldConfig]) => {
112
112
  if (typeof fieldConfig !== "object" || fieldConfig === null)
113
113
  return false;
114
114
  return "maxLength" in fieldConfig && fieldConfig.maxLength !== null || "max" in fieldConfig && fieldConfig.max !== null || "maxSize" in fieldConfig && fieldConfig.maxSize !== null;
@@ -140,7 +140,7 @@ class TLAGenerator {
140
140
  this.indent--;
141
141
  this.line("");
142
142
  }
143
- addStateType(config, analysis) {
143
+ addStateType(config, _analysis) {
144
144
  this.line("\\* Generic value type for sequences and maps");
145
145
  this.line("\\* Bounded to allow model checking");
146
146
  this.line('Value == {"v1", "v2", "v3"}');
@@ -169,7 +169,7 @@ class TLAGenerator {
169
169
  this.line("]");
170
170
  this.line("");
171
171
  }
172
- addMessageTypes(config, analysis) {
172
+ addMessageTypes(_config, analysis) {
173
173
  if (analysis.messageTypes.length === 0) {
174
174
  return;
175
175
  }
@@ -186,7 +186,7 @@ class TLAGenerator {
186
186
  this.line("allVars == <<ports, messages, pendingRequests, delivered, routingDepth, time, contextStates>>");
187
187
  this.line("");
188
188
  }
189
- addInit(config, analysis) {
189
+ addInit(config, _analysis) {
190
190
  this.line("\\* Initial application state");
191
191
  this.line("InitialState == [");
192
192
  this.indent++;
@@ -247,6 +247,8 @@ class TLAGenerator {
247
247
  const messageTypes = Array.from(handlersByType.keys());
248
248
  for (let i = 0;i < messageTypes.length; i++) {
249
249
  const msgType = messageTypes[i];
250
+ if (!msgType)
251
+ continue;
250
252
  const actionName = this.messageTypeToActionName(msgType);
251
253
  if (i === 0) {
252
254
  this.line(`IF msgType = "${msgType}" THEN ${actionName}(ctx)`);
@@ -304,6 +306,8 @@ class TLAGenerator {
304
306
  this.indent++;
305
307
  for (let i = 0;i < validAssignments.length; i++) {
306
308
  const assignment = validAssignments[i];
309
+ if (!assignment || assignment.value === undefined)
310
+ continue;
307
311
  const fieldName = this.sanitizeFieldName(assignment.field);
308
312
  const value = this.assignmentValueToTLA(assignment.value);
309
313
  const suffix = i < validAssignments.length - 1 ? "," : "";
@@ -325,10 +329,10 @@ class TLAGenerator {
325
329
  tsExpressionToTLA(expr, isPrimed = false) {
326
330
  let tla = expr;
327
331
  const statePrefix = isPrimed ? "contextStates'[ctx]" : "contextStates[ctx]";
328
- tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (match, path2) => {
332
+ tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path2) => {
329
333
  return `${statePrefix}.${this.sanitizeFieldName(path2)}`;
330
334
  });
331
- tla = tla.replace(/payload\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (match, path2) => {
335
+ tla = tla.replace(/payload\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path2) => {
332
336
  return `payload.${this.sanitizeFieldName(path2)}`;
333
337
  });
334
338
  tla = tla.replace(/===/g, "=");
@@ -366,7 +370,7 @@ class TLAGenerator {
366
370
  }
367
371
  return "NULL";
368
372
  }
369
- addRouteWithHandlers(config, analysis) {
373
+ addRouteWithHandlers(_config, analysis) {
370
374
  this.line("\\* =============================================================================");
371
375
  this.line("\\* Message Routing with State Transitions");
372
376
  this.line("\\* =============================================================================");
@@ -401,7 +405,7 @@ class TLAGenerator {
401
405
  this.indent--;
402
406
  this.line("");
403
407
  }
404
- addNext(config, analysis) {
408
+ addNext(_config, analysis) {
405
409
  this.line("\\* Next state relation (extends MessageRouter)");
406
410
  this.line("UserNext ==");
407
411
  this.indent++;
@@ -426,7 +430,7 @@ class TLAGenerator {
426
430
  this.line("UserSpec == UserInit /\\ [][UserNext]_allVars /\\ WF_allVars(UserNext)");
427
431
  this.line("");
428
432
  }
429
- addInvariants(config, analysis) {
433
+ addInvariants(_config, _analysis) {
430
434
  this.line("\\* =============================================================================");
431
435
  this.line("\\* Application Invariants");
432
436
  this.line("\\* =============================================================================");
@@ -450,7 +454,7 @@ class TLAGenerator {
450
454
  this.line("");
451
455
  this.line("=============================================================================");
452
456
  }
453
- fieldConfigToTLAType(fieldPath, fieldConfig, config) {
457
+ fieldConfigToTLAType(_fieldPath, fieldConfig, _config) {
454
458
  if ("type" in fieldConfig) {
455
459
  if (fieldConfig.type === "boolean") {
456
460
  return "BOOLEAN";
@@ -461,11 +465,9 @@ class TLAGenerator {
461
465
  }
462
466
  }
463
467
  if ("maxLength" in fieldConfig) {
464
- const constName = this.fieldToConstName(fieldPath);
465
468
  return `Seq(Value)`;
466
469
  }
467
470
  if ("min" in fieldConfig && "max" in fieldConfig) {
468
- const constName = this.fieldToConstName(fieldPath);
469
471
  const min = fieldConfig.min || 0;
470
472
  const max = fieldConfig.max || 100;
471
473
  return `${min}..${max}`;
@@ -538,7 +540,6 @@ import * as fs2 from "node:fs";
538
540
  import * as path2 from "node:path";
539
541
 
540
542
  class DockerRunner {
541
- containerName = "web-ext-tla-verify";
542
543
  async isDockerAvailable() {
543
544
  try {
544
545
  const result = await this.runCommand("docker", ["--version"]);
@@ -609,8 +610,8 @@ class DockerRunner {
609
610
  return {
610
611
  success: true,
611
612
  stats: {
612
- statesGenerated: statesMatch ? Number.parseInt(statesMatch[1]) : 0,
613
- distinctStates: distinctMatch ? Number.parseInt(distinctMatch[1]) : 0
613
+ statesGenerated: statesMatch?.[1] ? Number.parseInt(statesMatch[1]) : 0,
614
+ distinctStates: distinctMatch?.[1] ? Number.parseInt(distinctMatch[1]) : 0
614
615
  },
615
616
  output
616
617
  };
@@ -635,7 +636,7 @@ class DockerRunner {
635
636
  }
636
637
  extractError(output) {
637
638
  const errorMatch = output.match(/Error: (.*?)(?:\n|$)/);
638
- if (errorMatch) {
639
+ if (errorMatch?.[1]) {
639
640
  return errorMatch[1];
640
641
  }
641
642
  if (output.includes("Parse Error")) {
@@ -723,7 +724,7 @@ import * as path3 from "node:path";
723
724
  import { Project as Project2 } from "ts-morph";
724
725
 
725
726
  // vendor/analysis/src/extract/handlers.ts
726
- import { Project, SyntaxKind as SyntaxKind2, Node as Node2 } from "ts-morph";
727
+ import { Project, SyntaxKind, Node as Node2 } from "ts-morph";
727
728
 
728
729
  // vendor/analysis/src/extract/relationships.ts
729
730
  import { Node } from "ts-morph";
@@ -826,7 +827,7 @@ class RelationshipExtractor {
826
827
  const objectExpr = expr.getExpression();
827
828
  const objectName = objectExpr.getText();
828
829
  const methodName = expr.getName();
829
- targetComponent = this.inferComponentFromCall(objectName, methodName);
830
+ targetComponent = this.inferComponentFromCall(objectName);
830
831
  if (!targetComponent) {
831
832
  return null;
832
833
  }
@@ -857,7 +858,7 @@ class RelationshipExtractor {
857
858
  rootObject = rootObject.getExpression();
858
859
  }
859
860
  const objectName = rootObject.getText();
860
- const targetComponent = this.inferComponentFromCall(objectName, methodName);
861
+ const targetComponent = this.inferComponentFromCall(objectName);
861
862
  if (!targetComponent) {
862
863
  return null;
863
864
  }
@@ -894,7 +895,7 @@ class RelationshipExtractor {
894
895
  }
895
896
  extractFromFetchCall(callExpr, handlerName) {
896
897
  const args = callExpr.getArguments();
897
- if (args.length === 0) {
898
+ if (args.length === 0 || !args[0]) {
898
899
  return null;
899
900
  }
900
901
  const urlArg = args[0].getText();
@@ -913,7 +914,7 @@ class RelationshipExtractor {
913
914
  evidence: [`fetch() call to: ${urlArg}`]
914
915
  };
915
916
  }
916
- inferComponentFromCall(objectName, methodName) {
917
+ inferComponentFromCall(objectName) {
917
918
  const mappings = {
918
919
  db: "db_client",
919
920
  database: "database",
@@ -990,7 +991,7 @@ class RelationshipExtractor {
990
991
  }
991
992
  if (modulePath.includes("/service") || modulePath.includes("/services")) {
992
993
  const match = modulePath.match(/\/([^/]+)\.ts$/);
993
- if (match) {
994
+ if (match && match[1]) {
994
995
  return this.toComponentId(match[1]);
995
996
  }
996
997
  }
@@ -1074,8 +1075,9 @@ class HandlerExtractor {
1074
1075
  extractHandlers() {
1075
1076
  const handlers = [];
1076
1077
  const messageTypes = new Set;
1078
+ const invalidMessageTypes = new Set;
1077
1079
  const sourceFiles = this.project.getSourceFiles();
1078
- if (process.env.POLLY_DEBUG) {
1080
+ if (process.env["POLLY_DEBUG"]) {
1079
1081
  console.log(`[DEBUG] Loaded ${sourceFiles.length} source files`);
1080
1082
  if (sourceFiles.length <= 20) {
1081
1083
  for (const sf of sourceFiles) {
@@ -1087,17 +1089,30 @@ class HandlerExtractor {
1087
1089
  const fileHandlers = this.extractFromFile(sourceFile);
1088
1090
  handlers.push(...fileHandlers);
1089
1091
  for (const handler of fileHandlers) {
1090
- messageTypes.add(handler.messageType);
1092
+ if (this.isValidTLAIdentifier(handler.messageType)) {
1093
+ messageTypes.add(handler.messageType);
1094
+ } else {
1095
+ invalidMessageTypes.add(handler.messageType);
1096
+ }
1091
1097
  }
1092
1098
  }
1093
- if (process.env.POLLY_DEBUG) {
1099
+ if (process.env["POLLY_DEBUG"]) {
1094
1100
  console.log(`[DEBUG] Total handlers extracted: ${handlers.length}`);
1101
+ if (invalidMessageTypes.size > 0) {
1102
+ console.log(`[DEBUG] Filtered ${invalidMessageTypes.size} invalid message type(s) from handlers`);
1103
+ }
1095
1104
  }
1096
1105
  return {
1097
1106
  handlers,
1098
1107
  messageTypes
1099
1108
  };
1100
1109
  }
1110
+ isValidTLAIdentifier(s) {
1111
+ if (!s || s.length === 0) {
1112
+ return false;
1113
+ }
1114
+ return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(s);
1115
+ }
1101
1116
  extractFromFile(sourceFile) {
1102
1117
  const handlers = [];
1103
1118
  const filePath = sourceFile.getFilePath();
@@ -1207,7 +1222,7 @@ class HandlerExtractor {
1207
1222
  extractVerificationConditions(funcNode, preconditions, postconditions) {
1208
1223
  const body = funcNode.getBody();
1209
1224
  const statements = Node2.isBlock(body) ? body.getStatements() : [body];
1210
- statements.forEach((statement, index) => {
1225
+ statements.forEach((statement) => {
1211
1226
  if (Node2.isExpressionStatement(statement)) {
1212
1227
  const expr = statement.getExpression();
1213
1228
  if (Node2.isCallExpression(expr)) {
@@ -1271,13 +1286,13 @@ class HandlerExtractor {
1271
1286
  if (Node2.isNumericLiteral(node)) {
1272
1287
  return node.getLiteralValue();
1273
1288
  }
1274
- if (node.getKind() === SyntaxKind2.TrueKeyword) {
1289
+ if (node.getKind() === SyntaxKind.TrueKeyword) {
1275
1290
  return true;
1276
1291
  }
1277
- if (node.getKind() === SyntaxKind2.FalseKeyword) {
1292
+ if (node.getKind() === SyntaxKind.FalseKeyword) {
1278
1293
  return false;
1279
1294
  }
1280
- if (node.getKind() === SyntaxKind2.NullKeyword) {
1295
+ if (node.getKind() === SyntaxKind.NullKeyword) {
1281
1296
  return null;
1282
1297
  }
1283
1298
  return;
@@ -1360,7 +1375,7 @@ class HandlerExtractor {
1360
1375
  typeGuards = this.findTypePredicateFunctions(sourceFile);
1361
1376
  this.typeGuardCache.set(sourceFile, typeGuards);
1362
1377
  }
1363
- if (process.env.POLLY_DEBUG) {
1378
+ if (process.env["POLLY_DEBUG"]) {
1364
1379
  console.log(`[DEBUG] File: ${sourceFile.getBaseName()}`);
1365
1380
  console.log(`[DEBUG] Local type guards found: ${typeGuards.size}`);
1366
1381
  if (typeGuards.size > 0) {
@@ -1374,7 +1389,7 @@ class HandlerExtractor {
1374
1389
  const handler = this.extractHandlerFromIfClause(currentIf, typeGuards, context, filePath);
1375
1390
  if (handler) {
1376
1391
  handlers.push(handler);
1377
- if (process.env.POLLY_DEBUG) {
1392
+ if (process.env["POLLY_DEBUG"]) {
1378
1393
  console.log(`[DEBUG] Found handler: ${handler.messageType} at line ${handler.location.line}`);
1379
1394
  }
1380
1395
  }
@@ -1386,7 +1401,7 @@ class HandlerExtractor {
1386
1401
  }
1387
1402
  }
1388
1403
  } catch (error) {
1389
- if (process.env.POLLY_DEBUG) {
1404
+ if (process.env["POLLY_DEBUG"]) {
1390
1405
  console.log(`[DEBUG] Error in extractTypeGuardHandlers: ${error}`);
1391
1406
  }
1392
1407
  }
@@ -1394,7 +1409,8 @@ class HandlerExtractor {
1394
1409
  }
1395
1410
  extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
1396
1411
  try {
1397
- const condition = ifNode.getExpression();
1412
+ const ifStmt = ifNode;
1413
+ const condition = ifStmt.getExpression();
1398
1414
  if (!Node2.isCallExpression(condition)) {
1399
1415
  return null;
1400
1416
  }
@@ -1403,32 +1419,32 @@ class HandlerExtractor {
1403
1419
  if (Node2.isIdentifier(funcExpr)) {
1404
1420
  funcName = funcExpr.getText();
1405
1421
  }
1406
- if (process.env.POLLY_DEBUG && funcName) {
1422
+ if (process.env["POLLY_DEBUG"] && funcName) {
1407
1423
  console.log(`[DEBUG] Processing if condition with function: ${funcName}`);
1408
1424
  }
1409
1425
  let messageType = undefined;
1410
1426
  if (funcName && typeGuards.has(funcName)) {
1411
1427
  messageType = typeGuards.get(funcName);
1412
- if (process.env.POLLY_DEBUG) {
1428
+ if (process.env["POLLY_DEBUG"]) {
1413
1429
  console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
1414
1430
  }
1415
1431
  } else if (Node2.isIdentifier(funcExpr)) {
1416
- if (process.env.POLLY_DEBUG) {
1432
+ if (process.env["POLLY_DEBUG"]) {
1417
1433
  console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
1418
1434
  }
1419
- messageType = this.resolveImportedTypeGuard(funcExpr);
1435
+ messageType = this.resolveImportedTypeGuard(funcExpr) ?? undefined;
1420
1436
  }
1421
1437
  if (!messageType) {
1422
- if (process.env.POLLY_DEBUG && funcName) {
1438
+ if (process.env["POLLY_DEBUG"] && funcName) {
1423
1439
  console.log(`[DEBUG] Could not resolve message type for: ${funcName}`);
1424
1440
  }
1425
1441
  return null;
1426
1442
  }
1427
- const line = ifNode.getStartLineNumber();
1428
- const sourceFile = ifNode.getSourceFile();
1443
+ const line = ifStmt.getStartLineNumber();
1444
+ const sourceFile = ifStmt.getSourceFile();
1429
1445
  const handlerName = `${messageType}_handler`;
1430
1446
  let relationships = undefined;
1431
- const thenStatement = ifNode.getThenStatement();
1447
+ const thenStatement = ifStmt.getThenStatement();
1432
1448
  if (thenStatement) {
1433
1449
  const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
1434
1450
  if (detectedRelationships.length > 0) {
@@ -1481,7 +1497,7 @@ class HandlerExtractor {
1481
1497
  const bodyText = body.getText();
1482
1498
  const typeValueMatch = bodyText.match(/\.type\s*===?\s*['"](\w+)['"]/);
1483
1499
  if (typeValueMatch) {
1484
- messageType = typeValueMatch[1];
1500
+ messageType = typeValueMatch[1] ?? null;
1485
1501
  }
1486
1502
  }
1487
1503
  }
@@ -1499,7 +1515,7 @@ class HandlerExtractor {
1499
1515
  const funcName = identifier.getText();
1500
1516
  const definitions = identifier.getDefinitionNodes();
1501
1517
  if (definitions.length === 0) {
1502
- if (process.env.POLLY_DEBUG) {
1518
+ if (process.env["POLLY_DEBUG"]) {
1503
1519
  console.log(`[DEBUG] No definitions found for imported function: ${funcName}`);
1504
1520
  }
1505
1521
  return null;
@@ -1507,7 +1523,7 @@ class HandlerExtractor {
1507
1523
  for (const def of definitions) {
1508
1524
  if (Node2.isFunctionDeclaration(def) || Node2.isFunctionExpression(def) || Node2.isArrowFunction(def)) {
1509
1525
  const returnTypeNode = def.getReturnTypeNode();
1510
- if (process.env.POLLY_DEBUG) {
1526
+ if (process.env["POLLY_DEBUG"]) {
1511
1527
  const returnType = def.getReturnType().getText();
1512
1528
  console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
1513
1529
  console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
@@ -1519,7 +1535,7 @@ class HandlerExtractor {
1519
1535
  const typeName = typeNode.getText();
1520
1536
  const messageType = this.extractMessageTypeFromTypeName(typeName);
1521
1537
  if (messageType) {
1522
- if (process.env.POLLY_DEBUG) {
1538
+ if (process.env["POLLY_DEBUG"]) {
1523
1539
  console.log(`[DEBUG] Resolved ${funcName} → ${messageType} (from AST type predicate)`);
1524
1540
  }
1525
1541
  return messageType;
@@ -1531,8 +1547,8 @@ class HandlerExtractor {
1531
1547
  const bodyText = body.getText();
1532
1548
  const typeValueMatch = bodyText.match(/\.type\s*===?\s*['"](\w+)['"]/);
1533
1549
  if (typeValueMatch) {
1534
- const messageType = typeValueMatch[1];
1535
- if (process.env.POLLY_DEBUG) {
1550
+ const messageType = typeValueMatch[1] ?? null;
1551
+ if (process.env["POLLY_DEBUG"]) {
1536
1552
  console.log(`[DEBUG] Resolved ${funcName} → ${messageType} (from body)`);
1537
1553
  }
1538
1554
  return messageType;
@@ -1541,7 +1557,7 @@ class HandlerExtractor {
1541
1557
  }
1542
1558
  }
1543
1559
  } catch (error) {
1544
- if (process.env.POLLY_DEBUG) {
1560
+ if (process.env["POLLY_DEBUG"]) {
1545
1561
  console.log(`[DEBUG] Error resolving imported type guard: ${error}`);
1546
1562
  }
1547
1563
  }
@@ -1596,17 +1612,39 @@ class TypeExtractor {
1596
1612
  const stateType = stateFilePath ? this.extractStateType(stateFilePath) : this.findStateType();
1597
1613
  const messageTypes = this.findMessageTypes();
1598
1614
  const fields = stateType ? this.analyzeFields(stateType) : [];
1599
- const configFilePath = this.project.getCompilerOptions().configFilePath;
1615
+ const configFilePath = this.project.getCompilerOptions()["configFilePath"];
1600
1616
  const tsConfigPath = typeof configFilePath === "string" ? configFilePath : "tsconfig.json";
1601
1617
  const handlerExtractor = new HandlerExtractor(tsConfigPath);
1602
1618
  const handlerAnalysis = handlerExtractor.extractHandlers();
1619
+ const allMessageTypes = Array.from(new Set([...messageTypes, ...handlerAnalysis.messageTypes]));
1620
+ const validMessageTypes = [];
1621
+ const invalidMessageTypes = [];
1622
+ for (const msgType of allMessageTypes) {
1623
+ if (this.isValidTLAIdentifier(msgType)) {
1624
+ validMessageTypes.push(msgType);
1625
+ } else {
1626
+ invalidMessageTypes.push(msgType);
1627
+ }
1628
+ }
1629
+ if (invalidMessageTypes.length > 0 && process.env["POLLY_DEBUG"]) {
1630
+ console.log(`[WARN] Filtered out ${invalidMessageTypes.length} invalid message type(s):`);
1631
+ for (const invalid of invalidMessageTypes) {
1632
+ console.log(`[WARN] - "${invalid}" (not a valid TLA+ identifier)`);
1633
+ }
1634
+ }
1603
1635
  return {
1604
1636
  stateType,
1605
- messageTypes: Array.from(new Set([...messageTypes, ...handlerAnalysis.messageTypes])),
1637
+ messageTypes: validMessageTypes,
1606
1638
  fields,
1607
1639
  handlers: handlerAnalysis.handlers
1608
1640
  };
1609
1641
  }
1642
+ isValidTLAIdentifier(s) {
1643
+ if (!s || s.length === 0) {
1644
+ return false;
1645
+ }
1646
+ return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(s);
1647
+ }
1610
1648
  extractStateType(filePath) {
1611
1649
  const sourceFile = this.project.getSourceFile(filePath);
1612
1650
  if (!sourceFile) {
@@ -1724,10 +1762,13 @@ class TypeExtractor {
1724
1762
  }
1725
1763
  if (type.isObject()) {
1726
1764
  const properties = {};
1727
- for (const prop of type.getProperties()) {
1728
- const propName = prop.getName();
1729
- const propType = prop.getTypeAtLocation(this.project.getSourceFiles()[0]);
1730
- properties[propName] = this.convertType(propType, propName);
1765
+ const sourceFile = this.project.getSourceFiles()[0];
1766
+ if (sourceFile) {
1767
+ for (const prop of type.getProperties()) {
1768
+ const propName = prop.getName();
1769
+ const propType = prop.getTypeAtLocation(sourceFile);
1770
+ properties[propName] = this.convertType(propType, propName);
1771
+ }
1731
1772
  }
1732
1773
  return {
1733
1774
  name,
@@ -1780,7 +1821,7 @@ class TypeExtractor {
1780
1821
  analysis.confidence = "low";
1781
1822
  analysis.suggestions.push("Choose maxLength: 5 (fast), 10 (balanced), or 20 (thorough)");
1782
1823
  analysis.bounds.maxLength = undefined;
1783
- const foundBound = this.findArrayBound(path);
1824
+ const foundBound = this.findArrayBound();
1784
1825
  if (foundBound) {
1785
1826
  analysis.confidence = "medium";
1786
1827
  analysis.evidence.push(`Found array check: ${foundBound.evidence}`);
@@ -1793,7 +1834,7 @@ class TypeExtractor {
1793
1834
  analysis.suggestions.push("Provide min and max values based on your application logic");
1794
1835
  analysis.bounds.min = undefined;
1795
1836
  analysis.bounds.max = undefined;
1796
- const foundBound = this.findNumberBound(path);
1837
+ const foundBound = this.findNumberBound();
1797
1838
  if (foundBound) {
1798
1839
  analysis.confidence = "high";
1799
1840
  analysis.evidence.push(`Found comparison: ${foundBound.evidence}`);
@@ -1815,10 +1856,10 @@ class TypeExtractor {
1815
1856
  }
1816
1857
  return analysis;
1817
1858
  }
1818
- findArrayBound(path) {
1859
+ findArrayBound() {
1819
1860
  return null;
1820
1861
  }
1821
- findNumberBound(path) {
1862
+ findNumberBound() {
1822
1863
  return null;
1823
1864
  }
1824
1865
  }
@@ -1866,7 +1907,7 @@ class ConfigGenerator {
1866
1907
  this.line("");
1867
1908
  }
1868
1909
  addExport() {
1869
- this.line("export default defineVerification({");
1910
+ this.line("export const verificationConfig = defineVerification({");
1870
1911
  this.indent++;
1871
1912
  }
1872
1913
  closeExport() {
@@ -1878,6 +1919,8 @@ class ConfigGenerator {
1878
1919
  this.indent++;
1879
1920
  for (let i = 0;i < fields.length; i++) {
1880
1921
  const field = fields[i];
1922
+ if (!field)
1923
+ continue;
1881
1924
  if (i > 0) {
1882
1925
  this.line("");
1883
1926
  }
@@ -2174,13 +2217,13 @@ class ConfigValidator {
2174
2217
  const lineNumber = source.substring(0, position).split(`
2175
2218
  `).length;
2176
2219
  const line = lines[lineNumber - 1];
2177
- const fieldMatch = line.match(/"([^"]+)":\s*{/);
2220
+ const fieldMatch = line?.match(/"([^"]+)":\s*{/);
2178
2221
  const fieldName = fieldMatch ? fieldMatch[1] : "unknown";
2179
2222
  locations.push({
2180
2223
  line: lineNumber,
2181
2224
  column: match.index - source.lastIndexOf(`
2182
2225
  `, position),
2183
- context: fieldName
2226
+ context: fieldName ?? "unknown"
2184
2227
  });
2185
2228
  }
2186
2229
  this.issues.push({
@@ -2216,7 +2259,7 @@ class ConfigValidator {
2216
2259
  loadConfig(configPath) {
2217
2260
  delete __require.cache[__require.resolve(path.resolve(configPath))];
2218
2261
  const module = __require(path.resolve(configPath));
2219
- return module.default || module;
2262
+ return module.verificationConfig || module.default || module;
2220
2263
  }
2221
2264
  validateConfig(config) {
2222
2265
  this.findNullPlaceholders(config.state, "state");
@@ -2273,7 +2316,7 @@ class ConfigValidator {
2273
2316
  });
2274
2317
  }
2275
2318
  }
2276
- if (config.messages.maxTabs !== null) {
2319
+ if (config.messages.maxTabs !== null && config.messages.maxTabs !== undefined) {
2277
2320
  if (config.messages.maxTabs < 1) {
2278
2321
  this.issues.push({
2279
2322
  type: "invalid_value",
@@ -2448,7 +2491,7 @@ async function setupCommand() {
2448
2491
  table.push([field.path, field.type.kind, status]);
2449
2492
  }
2450
2493
  for (const row of table) {
2451
- console.log(` ${row[0].padEnd(32)} ${row[1].padEnd(22)} ${row[2]}`);
2494
+ console.log(` ${row[0]?.padEnd(32) ?? ""} ${row[1]?.padEnd(22) ?? ""} ${row[2] ?? ""}`);
2452
2495
  }
2453
2496
  }
2454
2497
  const configContent = generateConfig(analysis);
@@ -2565,9 +2608,9 @@ async function verifyCommand() {
2565
2608
  async function runFullVerification(configPath) {
2566
2609
  const { generateTLA: generateTLA2 } = await Promise.resolve().then(() => exports_tla);
2567
2610
  const { DockerRunner: DockerRunner2 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
2568
- delete __require.cache[__require.resolve(path3.resolve(configPath))];
2569
- const configModule = __require(path3.resolve(configPath));
2570
- const config = configModule.default || configModule;
2611
+ const resolvedPath = path3.resolve(configPath);
2612
+ const configModule = await import(`file://${resolvedPath}?t=${Date.now()}`);
2613
+ const config = configModule.default;
2571
2614
  console.log(color("\uD83D\uDCCA Analyzing codebase...", COLORS.blue));
2572
2615
  const tsConfigPath = findTsConfig();
2573
2616
  if (!tsConfigPath) {
@@ -2743,4 +2786,4 @@ Stack trace:`, COLORS.gray));
2743
2786
  process.exit(1);
2744
2787
  });
2745
2788
 
2746
- //# debugId=1F63934BFA65A24864756E2164756E21
2789
+ //# debugId=865A93F68683B62A64756E2164756E21