@fairfox/polly 0.85.1 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/cli/polly.js.map +1 -1
  2. package/dist/gallery/gallery.css +1 -1
  3. package/dist/gallery/gallery.js +2 -27
  4. package/dist/scripts/build-extension.js.map +1 -1
  5. package/dist/src/actions/index.js +34 -32
  6. package/dist/src/actions/index.js.map +2 -2
  7. package/dist/src/background/index.js +24 -22
  8. package/dist/src/background/index.js.map +2 -2
  9. package/dist/src/background/message-router.js +21 -19
  10. package/dist/src/background/message-router.js.map +2 -2
  11. package/dist/src/client/index.js +9 -7
  12. package/dist/src/client/index.js.map +3 -3
  13. package/dist/src/client/wrapper.d.ts +3 -3
  14. package/dist/src/elysia/index.d.ts +1 -1
  15. package/dist/src/elysia/index.js +25 -23
  16. package/dist/src/elysia/index.js.map +2 -2
  17. package/dist/src/elysia/types.d.ts +11 -2
  18. package/dist/src/index.js +68 -64
  19. package/dist/src/index.js.map +3 -3
  20. package/dist/src/mesh-node.js +6 -6
  21. package/dist/src/mesh-node.js.map +1 -1
  22. package/dist/src/mesh.js +92 -90
  23. package/dist/src/mesh.js.map +2 -2
  24. package/dist/src/peer.js +83 -52
  25. package/dist/src/peer.js.map +3 -3
  26. package/dist/src/polly-ui/index.css +1 -1
  27. package/dist/src/polly-ui/index.js +37 -35
  28. package/dist/src/polly-ui/index.js.map +2 -2
  29. package/dist/src/polly-ui/markdown.js +233 -96
  30. package/dist/src/polly-ui/markdown.js.map +4 -4
  31. package/dist/src/polly-ui/styles.css +1 -1
  32. package/dist/src/shared/adapters/index.js +30 -28
  33. package/dist/src/shared/adapters/index.js.map +2 -2
  34. package/dist/src/shared/lib/context-helpers.js +23 -21
  35. package/dist/src/shared/lib/context-helpers.js.map +2 -2
  36. package/dist/src/shared/lib/errors.js +16 -14
  37. package/dist/src/shared/lib/errors.js.map +2 -2
  38. package/dist/src/shared/lib/guards.js +11 -9
  39. package/dist/src/shared/lib/guards.js.map +2 -2
  40. package/dist/src/shared/lib/message-bus.js +24 -22
  41. package/dist/src/shared/lib/message-bus.js.map +2 -2
  42. package/dist/src/shared/lib/peer-relay-adapter.d.ts +6 -0
  43. package/dist/src/shared/lib/resource.js +19 -15
  44. package/dist/src/shared/lib/resource.js.map +3 -3
  45. package/dist/src/shared/lib/state.js +26 -22
  46. package/dist/src/shared/lib/state.js.map +3 -3
  47. package/dist/src/shared/lib/test-helpers.js +11 -9
  48. package/dist/src/shared/lib/test-helpers.js.map +2 -2
  49. package/dist/src/shared/state/app-state.js +22 -18
  50. package/dist/src/shared/state/app-state.js.map +3 -3
  51. package/dist/src/shared/types/messages.js +11 -9
  52. package/dist/src/shared/types/messages.js.map +2 -2
  53. package/dist/tools/bdd/src/cli.js +44 -1
  54. package/dist/tools/bdd/src/cli.js.map +4 -4
  55. package/dist/tools/bdd/src/index.js +54 -11
  56. package/dist/tools/bdd/src/index.js.map +4 -4
  57. package/dist/tools/gallery/src/cli.js +1 -1
  58. package/dist/tools/gallery/src/cli.js.map +2 -2
  59. package/dist/tools/init/src/cli.js +1 -1
  60. package/dist/tools/init/src/cli.js.map +2 -2
  61. package/dist/tools/mutate/src/cli.js +1 -1
  62. package/dist/tools/mutate/src/cli.js.map +2 -2
  63. package/dist/tools/mutate/src/index.js +8 -8
  64. package/dist/tools/mutate/src/index.js.map +2 -2
  65. package/dist/tools/quality/src/cli.js +3 -2
  66. package/dist/tools/quality/src/cli.js.map +3 -3
  67. package/dist/tools/quality/src/index.js +37 -36
  68. package/dist/tools/quality/src/index.js.map +3 -3
  69. package/dist/tools/test/src/adapters/index.js +19 -17
  70. package/dist/tools/test/src/adapters/index.js.map +2 -2
  71. package/dist/tools/test/src/browser/index.js +15 -13
  72. package/dist/tools/test/src/browser/index.js.map +2 -2
  73. package/dist/tools/test/src/browser/run.js +12 -3
  74. package/dist/tools/test/src/browser/run.js.map +5 -4
  75. package/dist/tools/test/src/contrast/index.js +14 -12
  76. package/dist/tools/test/src/contrast/index.js.map +2 -2
  77. package/dist/tools/test/src/coverage-policy/cli.js +5 -5
  78. package/dist/tools/test/src/coverage-policy/cli.js.map +2 -2
  79. package/dist/tools/test/src/coverage-policy/index.js +12 -12
  80. package/dist/tools/test/src/coverage-policy/index.js.map +1 -1
  81. package/dist/tools/test/src/e2e-mesh/index.js +88 -50
  82. package/dist/tools/test/src/e2e-mesh/index.js.map +6 -5
  83. package/dist/tools/test/src/e2e-mesh/with-relay.d.ts +1 -1
  84. package/dist/tools/test/src/e2e-relay/index.js +3 -3
  85. package/dist/tools/test/src/e2e-relay/index.js.map +2 -2
  86. package/dist/tools/test/src/e2e-shared/ephemeral-port.d.ts +73 -0
  87. package/dist/tools/test/src/e2e-shared/index.d.ts +1 -0
  88. package/dist/tools/test/src/index.d.ts +1 -0
  89. package/dist/tools/test/src/index.js +68 -24
  90. package/dist/tools/test/src/index.js.map +4 -3
  91. package/dist/tools/test/src/test-utils.js +14 -12
  92. package/dist/tools/test/src/test-utils.js.map +2 -2
  93. package/dist/tools/test/src/tiers/budget.d.ts +32 -0
  94. package/dist/tools/test/src/tiers/cli.js +35 -5
  95. package/dist/tools/test/src/tiers/cli.js.map +6 -5
  96. package/dist/tools/test/src/tiers/index.d.ts +1 -0
  97. package/dist/tools/test/src/tiers/reporter.d.ts +0 -5
  98. package/dist/tools/test/src/tiers/types.d.ts +9 -0
  99. package/dist/tools/test/src/tiers/worker.js +1 -1
  100. package/dist/tools/test/src/tiers/worker.js.map +1 -1
  101. package/dist/tools/test/src/visual/index.js +121 -119
  102. package/dist/tools/test/src/visual/index.js.map +2 -2
  103. package/dist/tools/verify/specs/README.md +18 -0
  104. package/dist/tools/verify/specs/tla/MeshState.cfg +1 -1
  105. package/dist/tools/verify/specs/tla/MeshState.tla +7 -0
  106. package/dist/tools/verify/specs/tla/MessageRouter.cfg +4 -2
  107. package/dist/tools/verify/specs/tla/MessageRouter.tla +45 -10
  108. package/dist/tools/verify/specs/tla/PeerState.cfg +1 -1
  109. package/dist/tools/verify/specs/tla/PeerState.tla +7 -0
  110. package/dist/tools/verify/specs/tla/README.md +1 -1
  111. package/dist/tools/verify/src/cli.js +258 -202
  112. package/dist/tools/verify/src/cli.js.map +12 -11
  113. package/dist/tools/verify/src/config.d.ts +25 -0
  114. package/dist/tools/verify/src/config.js +18 -16
  115. package/dist/tools/verify/src/config.js.map +3 -3
  116. package/dist/tools/verify/src/stryker/index.js +6 -6
  117. package/dist/tools/verify/src/stryker/index.js.map +1 -1
  118. package/dist/tools/visualize/src/cli.js +439 -47
  119. package/dist/tools/visualize/src/cli.js.map +7 -5
  120. package/package.json +2 -2
@@ -252,8 +252,8 @@ var init_expression_validator = __esm(() => {
252
252
  // tools/verify/src/analysis/model-coverage.ts
253
253
  var exports_model_coverage = {};
254
254
  __export(exports_model_coverage, {
255
- strictCoverageReasons: () => strictCoverageReasons,
256
- computeModelCoverage: () => computeModelCoverage
255
+ computeModelCoverage: () => computeModelCoverage,
256
+ strictCoverageReasons: () => strictCoverageReasons
257
257
  });
258
258
  function norm(field) {
259
259
  return field.replace(/_/g, ".");
@@ -504,150 +504,11 @@ var init_precondition_locality = __esm(() => {
504
504
  });
505
505
 
506
506
  // tools/verify/src/codegen/invariants.ts
507
- import { Node as Node3, Project as Project3 } from "ts-morph";
508
-
509
- class InvariantExtractor {
510
- warnings = [];
511
- extractInvariants(projectPath) {
512
- const project = new Project3({
513
- tsConfigFilePath: `${projectPath}/tsconfig.json`,
514
- skipAddingFilesFromTsConfig: false
515
- });
516
- const invariants = [];
517
- for (const sourceFile of project.getSourceFiles()) {
518
- if (sourceFile.getFilePath().includes("node_modules") || sourceFile.getFilePath().includes(".test.") || sourceFile.getFilePath().includes(".spec.")) {
519
- continue;
520
- }
521
- invariants.push(...this.extractFromSourceFile(sourceFile));
522
- }
523
- return {
524
- invariants,
525
- warnings: this.warnings
526
- };
527
- }
528
- extractFromAnalysis(_analysis) {
529
- return {
530
- invariants: [],
531
- warnings: []
532
- };
533
- }
534
- extractFromSourceFile(sourceFile) {
535
- const invariants = [];
536
- sourceFile.forEachDescendant((node) => {
537
- this.processNodeJSDocs(node, sourceFile, invariants);
538
- });
539
- return invariants;
540
- }
541
- processNodeJSDocs(node, sourceFile, invariants) {
542
- const jsDocs = this.getJsDocComments(node);
543
- for (const jsDoc of jsDocs) {
544
- this.processJSDocTags(jsDoc, sourceFile, node, invariants);
545
- }
546
- }
547
- processJSDocTags(jsDoc, sourceFile, node, invariants) {
548
- for (const tag of jsDoc.getTags()) {
549
- this.processJSDocTag(tag, sourceFile, node, invariants);
550
- }
551
- }
552
- processJSDocTag(tag, sourceFile, node, invariants) {
553
- const tagName = tag.getTagName();
554
- const text = tag.getComment();
555
- if (typeof text !== "string" || !text.trim()) {
556
- return;
557
- }
558
- const trimmedText = text.trim();
559
- if (tagName === "invariant") {
560
- invariants.push(this.createInvariant(trimmedText, "invariant", sourceFile, node));
561
- } else if (tagName === "ensures") {
562
- invariants.push(this.createInvariant(trimmedText, "post-condition", sourceFile, node));
563
- } else if (tagName === "requires") {
564
- invariants.push(this.createInvariant(trimmedText, "pre-condition", sourceFile, node));
565
- }
566
- }
567
- getJsDocComments(node) {
568
- if (Node3.isJSDocable(node)) {
569
- return node.getJsDocs();
570
- }
507
+ function describe(description) {
508
+ if (!description?.trim())
571
509
  return [];
572
- }
573
- createInvariant(expression, type, sourceFile, node) {
574
- const description = this.extractDescription(node, expression);
575
- const name = this.generateInvariantName(expression, type);
576
- return {
577
- name,
578
- description,
579
- expression,
580
- location: {
581
- file: sourceFile.getFilePath(),
582
- line: node.getStartLineNumber()
583
- }
584
- };
585
- }
586
- extractDescription(node, expression) {
587
- if (Node3.isJSDocable(node)) {
588
- const jsDocs = node.getJsDocs();
589
- for (const jsDoc of jsDocs) {
590
- const desc = jsDoc.getDescription();
591
- if (desc?.trim()) {
592
- return desc.trim();
593
- }
594
- }
595
- }
596
- return `Invariant: ${expression}`;
597
- }
598
- generateInvariantName(expression, type) {
599
- const fieldMatches = expression.match(/state\.(\w+)/g);
600
- if (!fieldMatches || fieldMatches.length === 0) {
601
- return this.generateGenericInvariantName(type);
602
- }
603
- const fieldName = fieldMatches[0]?.replace("state.", "");
604
- const constraintSuffix = this.determineConstraintSuffix(expression);
605
- const capitalizedField = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
606
- const prefix = this.getInvariantPrefix(type);
607
- return `${prefix}${capitalizedField}${constraintSuffix}`;
608
- }
609
- generateGenericInvariantName(type) {
610
- const prefix = this.getInvariantPrefix(type);
611
- return `${prefix}Invariant${Math.random().toString(36).substring(2, 7)}`;
612
- }
613
- determineConstraintSuffix(expression) {
614
- if (expression.includes("<="))
615
- return "MaxValue";
616
- if (expression.includes(">="))
617
- return "MinValue";
618
- if (expression.includes("===") || expression.includes("=="))
619
- return "Equals";
620
- if (expression.includes("!==") || expression.includes("!="))
621
- return "NotEquals";
622
- if (expression.includes("<"))
623
- return "LessThan";
624
- if (expression.includes(">"))
625
- return "GreaterThan";
626
- return "Check";
627
- }
628
- getInvariantPrefix(type) {
629
- if (type === "pre-condition")
630
- return "Pre";
631
- if (type === "post-condition")
632
- return "Post";
633
- return "";
634
- }
635
- validateExpression(expression) {
636
- if (expression.includes("eval(")) {
637
- return { valid: false, error: "eval() not allowed in invariants" };
638
- }
639
- const openParens = (expression.match(/\(/g) || []).length;
640
- const closeParens = (expression.match(/\)/g) || []).length;
641
- if (openParens !== closeParens) {
642
- return { valid: false, error: "Unbalanced parentheses" };
643
- }
644
- const openBrackets = (expression.match(/\[/g) || []).length;
645
- const closeBrackets = (expression.match(/\]/g) || []).length;
646
- if (openBrackets !== closeBrackets) {
647
- return { valid: false, error: "Unbalanced brackets" };
648
- }
649
- return { valid: true };
650
- }
510
+ return description.split(`
511
+ `).map((line) => `\\* ${line.trimEnd()}`);
651
512
  }
652
513
 
653
514
  class InvariantGenerator {
@@ -660,8 +521,8 @@ class InvariantGenerator {
660
521
  }
661
522
  generateSingleInvariant(invariant, tsExpressionToTLA) {
662
523
  const lines = [];
663
- if (invariant.description) {
664
- lines.push(`\\* ${invariant.description}`);
524
+ for (const line of describe(invariant.description)) {
525
+ lines.push(line);
665
526
  }
666
527
  const tlaExpr = tsExpressionToTLA(invariant.expression);
667
528
  lines.push(`${invariant.name} ==`);
@@ -673,7 +534,6 @@ class InvariantGenerator {
673
534
  return invariants.map((inv) => `INVARIANT ${inv.name}`);
674
535
  }
675
536
  }
676
- var init_invariants = () => {};
677
537
 
678
538
  // tools/verify/src/codegen/temporal.ts
679
539
  class TemporalPropertyGenerator {
@@ -773,8 +633,8 @@ class TemporalTLAGenerator {
773
633
  }
774
634
  generateSingleProperty(prop) {
775
635
  const lines = [];
776
- if (prop.description) {
777
- lines.push(`\\* ${prop.description}`);
636
+ for (const line of describe(prop.description)) {
637
+ lines.push(line);
778
638
  }
779
639
  switch (prop.type) {
780
640
  case "eventually":
@@ -829,14 +689,15 @@ class TemporalTLAGenerator {
829
689
  `);
830
690
  }
831
691
  }
692
+ var init_temporal = () => {};
832
693
 
833
694
  // tools/verify/src/codegen/tla.ts
834
695
  var exports_tla = {};
835
696
  __export(exports_tla, {
836
- generateTLA: () => generateTLA,
837
- generateSubsystemTLA: () => generateSubsystemTLA,
697
+ TLAGenerator: () => TLAGenerator,
838
698
  TLAValidationError: () => TLAValidationError,
839
- TLAGenerator: () => TLAGenerator
699
+ generateSubsystemTLA: () => generateSubsystemTLA,
700
+ generateTLA: () => generateTLA
840
701
  });
841
702
  import * as fs2 from "node:fs";
842
703
  import * as os from "node:os";
@@ -890,6 +751,7 @@ async function generateSubsystemTLA(_subsystemName, subsystem, config, analysis)
890
751
  ...config,
891
752
  state: filteredState,
892
753
  messages: filteredMessages,
754
+ liveness: subsystem.liveness ?? config.liveness,
893
755
  subsystems: undefined
894
756
  };
895
757
  if (filteredConfig.tier2?.temporalConstraints) {
@@ -917,7 +779,7 @@ async function generateSubsystemTLA(_subsystemName, subsystem, config, analysis)
917
779
  }
918
780
  var TLAValidationError, TLAGenerator;
919
781
  var init_tla = __esm(() => {
920
- init_invariants();
782
+ init_temporal();
921
783
  TLAValidationError = class TLAValidationError extends Error {
922
784
  report;
923
785
  constructor(message, report) {
@@ -932,6 +794,7 @@ var init_tla = __esm(() => {
932
794
  indent = 0;
933
795
  extractedInvariants = [];
934
796
  temporalProperties = [];
797
+ livenessPropertyNames = [];
935
798
  symmetrySets = [];
936
799
  resolvedActionNames = new Map;
937
800
  paramDomains = new Map;
@@ -970,10 +833,10 @@ var init_tla = __esm(() => {
970
833
  }
971
834
  }
972
835
  this.validateInputs(config, analysis);
973
- this.extractInvariantsIfEnabled();
974
836
  this.generateTemporalPropertiesIfEnabled(analysis);
975
837
  this.lines = [];
976
838
  this.indent = 0;
839
+ this.livenessPropertyNames = [];
977
840
  const spec = this.generateSpec(config, analysis);
978
841
  const cfg = this.generateConfig(config);
979
842
  if (!this.hasValidators()) {
@@ -985,19 +848,6 @@ var init_tla = __esm(() => {
985
848
  hasValidators() {
986
849
  return !!(this.options?.validator || this.options?.sanyRunner || this.options?.roundTripValidator);
987
850
  }
988
- extractInvariantsIfEnabled() {
989
- if (this.options?.enableInvariants && this.options.projectPath) {
990
- const extractor = new InvariantExtractor;
991
- const result = extractor.extractInvariants(this.options.projectPath);
992
- this.extractedInvariants = result.invariants;
993
- if (result.warnings.length > 0 && process.env["POLLY_DEBUG"]) {
994
- console.log("[DEBUG] Invariant extraction warnings:");
995
- for (const warning of result.warnings) {
996
- console.log(` - ${warning}`);
997
- }
998
- }
999
- }
1000
- }
1001
851
  generateTemporalPropertiesIfEnabled(analysis) {
1002
852
  if (this.options?.enableTemporalProperties) {
1003
853
  const generator = new TemporalPropertyGenerator;
@@ -1129,11 +979,12 @@ var init_tla = __esm(() => {
1129
979
  this.addActions(config, analysis);
1130
980
  this.addRouteWithHandlers(config, analysis);
1131
981
  this.addNext(config, analysis);
1132
- this.addSpec();
982
+ this.addSpec(config, analysis);
1133
983
  this.addInvariants(config, analysis);
1134
984
  if (this.temporalProperties.length > 0) {
1135
985
  this.addTemporalProperties();
1136
986
  }
987
+ this.addLivenessProperties(config, analysis);
1137
988
  this.line("=============================================================================");
1138
989
  return this.lines.join(`
1139
990
  `);
@@ -1230,7 +1081,7 @@ var init_tla = __esm(() => {
1230
1081
  }
1231
1082
  }
1232
1083
  addTemporalPropertiesSection(lines) {
1233
- if (this.temporalProperties.length === 0)
1084
+ if (this.temporalProperties.length === 0 && this.livenessPropertyNames.length === 0)
1234
1085
  return;
1235
1086
  lines.push("");
1236
1087
  lines.push("\\* Temporal properties to check");
@@ -1238,6 +1089,9 @@ var init_tla = __esm(() => {
1238
1089
  for (const prop of this.temporalProperties) {
1239
1090
  lines.push(` ${prop.name}`);
1240
1091
  }
1092
+ for (const name of this.livenessPropertyNames) {
1093
+ lines.push(` ${name}`);
1094
+ }
1241
1095
  }
1242
1096
  addConstraintSection(lines) {
1243
1097
  lines.push("");
@@ -2742,6 +2596,7 @@ var init_tla = __esm(() => {
2742
2596
  this.line(" /\\ routingDepth < 5");
2743
2597
  this.line(" /\\ \\E target \\in msg.targets :");
2744
2598
  this.line(' /\\ IF target \\in Contexts /\\ ports[target] = "connected"');
2599
+ this.line(" /\\ ENABLED StateTransition(target, msg.msgType)");
2745
2600
  this.line(" THEN \\* Successful delivery - route AND invoke handler");
2746
2601
  this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "delivered",`);
2747
2602
  this.line(" ![msgIndex].deliveredTo = target]");
@@ -2751,7 +2606,7 @@ var init_tla = __esm(() => {
2751
2606
  this.line(" /\\ time' = time + 1");
2752
2607
  this.line(" /\\ \\E p \\in PayloadType : payload' = p \\* Non-deterministic payload");
2753
2608
  this.line(" /\\ StateTransition(target, msg.msgType)");
2754
- this.line(" ELSE \\* Port not connected - message fails");
2609
+ this.line(" ELSE \\* Port not connected, or the handler refused it");
2755
2610
  this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "failed"]`);
2756
2611
  this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
2757
2612
  this.line(" pendingRequests[id]]");
@@ -2802,9 +2657,50 @@ var init_tla = __esm(() => {
2802
2657
  this.indent--;
2803
2658
  this.line("");
2804
2659
  }
2805
- addSpec() {
2660
+ livenessEnabled(config) {
2661
+ return config.liveness === true;
2662
+ }
2663
+ routingIsReachable(analysis) {
2664
+ return analysis.handlers.some((h) => this.canRepresentAsTLAAction(h.messageType));
2665
+ }
2666
+ addSpec(config, analysis) {
2806
2667
  this.line("\\* Specification");
2807
- this.line("UserSpec == UserInit /\\ [][UserNext]_allVars /\\ WF_allVars(UserNext)");
2668
+ const liveness = this.livenessEnabled(config) && this.routingIsReachable(analysis);
2669
+ if (!liveness) {
2670
+ this.line("UserSpec == UserInit /\\ [][UserNext]_allVars /\\ WF_allVars(UserNext)");
2671
+ this.line("");
2672
+ return;
2673
+ }
2674
+ this.line("UserSpec ==");
2675
+ this.indent++;
2676
+ this.line("/\\ UserInit");
2677
+ this.line("/\\ [][UserNext]_allVars");
2678
+ this.line("/\\ WF_allVars(UserNext)");
2679
+ this.line("/\\ \\A i \\in 1..MaxMessages : WF_allVars(UserRouteMessage(i))");
2680
+ this.indent--;
2681
+ this.line("");
2682
+ }
2683
+ addLivenessProperties(config, analysis) {
2684
+ if (!this.livenessEnabled(config) || !this.routingIsReachable(analysis))
2685
+ return;
2686
+ const name = "NoMessageStaysPending";
2687
+ this.livenessPropertyNames.push(name);
2688
+ this.line("\\* =============================================================================");
2689
+ this.line("\\* Liveness (polly#171)");
2690
+ this.line("\\* =============================================================================");
2691
+ this.line("");
2692
+ this.line("\\* Every message that reaches the router is eventually resolved.");
2693
+ this.line("\\* A requires() that does not hold marks the message failed rather than");
2694
+ this.line("\\* leaving it pending for ever, so this is checkable.");
2695
+ this.line(`${name} ==`);
2696
+ this.indent++;
2697
+ this.line("\\A i \\in 1..MaxMessages :");
2698
+ this.indent++;
2699
+ this.line('(Len(messages) >= i /\\ messages[i].status = "pending")');
2700
+ this.line("~> (Len(messages) >= i");
2701
+ this.line(' /\\ messages[i].status \\in {"delivered", "failed", "timeout"})');
2702
+ this.indent--;
2703
+ this.indent--;
2808
2704
  this.line("");
2809
2705
  }
2810
2706
  addInvariants(config, _analysis) {
@@ -2827,7 +2723,7 @@ var init_tla = __esm(() => {
2827
2723
  this.addCapabilityInvariants(config.capabilities);
2828
2724
  }
2829
2725
  if (this.extractedInvariants.length > 0) {
2830
- this.line("\\* Extracted invariants from code annotations");
2726
+ this.line("\\* Synthesised invariants");
2831
2727
  this.line("");
2832
2728
  const invGenerator = new InvariantGenerator;
2833
2729
  const tlaInvariants = invGenerator.generateTLAInvariants(this.extractedInvariants, (expr) => this.tsExpressionToTLA(expr, false));
@@ -3390,19 +3286,19 @@ var init_witness = __esm(() => {
3390
3286
  // tools/verify/src/codegen/witness.ts
3391
3287
  var exports_witness2 = {};
3392
3288
  __export(exports_witness2, {
3393
- witnessVerdict: () => witnessVerdict,
3394
- witnessSpecLocation: () => witnessSpecLocation,
3395
- witnessPolarity: () => witnessPolarity,
3396
- routeWitness: () => routeWitness,
3397
- parseModuleName: () => parseModuleName,
3398
- buildWitnessModule: () => buildWitnessModule,
3399
- buildWitnessInvariantBare: () => buildWitnessInvariantBare,
3400
- buildWitnessInvariant: () => buildWitnessInvariant,
3401
- buildWitnessCfg: () => buildWitnessCfg,
3402
- bddPredicateToTLA: () => bddPredicateToTLA,
3403
- bareFieldRenderer: () => bareFieldRenderer,
3289
+ WITNESS_INVARIANT: () => WITNESS_INVARIANT,
3404
3290
  WitnessTranslationError: () => WitnessTranslationError,
3405
- WITNESS_INVARIANT: () => WITNESS_INVARIANT
3291
+ bareFieldRenderer: () => bareFieldRenderer,
3292
+ bddPredicateToTLA: () => bddPredicateToTLA,
3293
+ buildWitnessCfg: () => buildWitnessCfg,
3294
+ buildWitnessInvariant: () => buildWitnessInvariant,
3295
+ buildWitnessInvariantBare: () => buildWitnessInvariantBare,
3296
+ buildWitnessModule: () => buildWitnessModule,
3297
+ parseModuleName: () => parseModuleName,
3298
+ routeWitness: () => routeWitness,
3299
+ witnessPolarity: () => witnessPolarity,
3300
+ witnessSpecLocation: () => witnessSpecLocation,
3301
+ witnessVerdict: () => witnessVerdict
3406
3302
  });
3407
3303
  import * as path3 from "node:path";
3408
3304
  function witnessPolarity(tags) {
@@ -3552,9 +3448,12 @@ function constantBody(cfg) {
3552
3448
  function headerLine(cfg, header) {
3553
3449
  for (const line of cfg.split(`
3554
3450
  `)) {
3555
- const m = new RegExp(`^${header}\\b(.*)$`).exec(line);
3556
- if (m)
3557
- return line.trimEnd();
3451
+ if (!line.startsWith(header))
3452
+ continue;
3453
+ const next = line.charAt(header.length);
3454
+ if (next !== "" && /\w/.test(next))
3455
+ continue;
3456
+ return line.trimEnd();
3558
3457
  }
3559
3458
  return null;
3560
3459
  }
@@ -3609,14 +3508,62 @@ var init_witness2 = __esm(() => {
3609
3508
  ];
3610
3509
  });
3611
3510
 
3511
+ // tools/verify/src/runner/container-name.ts
3512
+ function mintContainerName(kind) {
3513
+ counter += 1;
3514
+ const unique = crypto.randomUUID().slice(0, 8);
3515
+ return `${CONTAINER_NAME_PREFIX}${sanitizeKind(kind)}-${process.pid}-${counter}-${unique}`;
3516
+ }
3517
+ function sanitizeKind(kind) {
3518
+ const cleaned = kind.replace(/[^a-zA-Z0-9_.-]/g, "-");
3519
+ return cleaned.length > 0 ? cleaned : "run";
3520
+ }
3521
+ var CONTAINER_NAME_PREFIX = "polly-tla-", counter = 0;
3522
+
3612
3523
  // tools/verify/src/runner/docker.ts
3613
3524
  var exports_docker = {};
3614
3525
  __export(exports_docker, {
3615
- DockerRunner: () => DockerRunner
3526
+ DockerRunner: () => DockerRunner,
3527
+ removeContainer: () => removeContainer,
3528
+ sweepOrphanedContainers: () => sweepOrphanedContainers,
3529
+ sweepOrphanedContainersOnce: () => sweepOrphanedContainersOnce
3616
3530
  });
3617
- import { spawn } from "node:child_process";
3531
+ import { spawn, spawnSync } from "node:child_process";
3618
3532
  import * as fs3 from "node:fs";
3619
3533
  import * as path4 from "node:path";
3534
+ function removeContainer(name) {
3535
+ try {
3536
+ spawnSync("docker", ["rm", "-f", name], { timeout: 1e4, stdio: "ignore" });
3537
+ } catch {}
3538
+ }
3539
+ function sweepOrphanedContainersOnce() {
3540
+ if (swept)
3541
+ return [];
3542
+ swept = true;
3543
+ return sweepOrphanedContainers();
3544
+ }
3545
+ function sweepOrphanedContainers() {
3546
+ try {
3547
+ const listed = spawnSync("docker", [
3548
+ "ps",
3549
+ "--all",
3550
+ "--filter",
3551
+ "status=created",
3552
+ "--filter",
3553
+ `name=${CONTAINER_NAME_PREFIX}`,
3554
+ "--format",
3555
+ "{{.Names}}"
3556
+ ], { timeout: 1e4, encoding: "utf8" });
3557
+ const names = (listed.stdout ?? "").split(`
3558
+ `).map((line) => line.trim()).filter((line) => line.startsWith(CONTAINER_NAME_PREFIX));
3559
+ for (const name of names) {
3560
+ removeContainer(name);
3561
+ }
3562
+ return names;
3563
+ } catch {
3564
+ return [];
3565
+ }
3566
+ }
3620
3567
 
3621
3568
  class DockerRunner {
3622
3569
  IMAGE_NAME = "polly-tla:latest";
@@ -3668,9 +3615,13 @@ class DockerRunner {
3668
3615
  if (fs3.existsSync(statesDir)) {
3669
3616
  fs3.rmSync(statesDir, { recursive: true, force: true });
3670
3617
  }
3618
+ const containerName = mintContainerName("tlc");
3619
+ sweepOrphanedContainersOnce();
3671
3620
  const args = [
3672
3621
  "run",
3673
3622
  "--rm",
3623
+ "--name",
3624
+ containerName,
3674
3625
  "-v",
3675
3626
  `${specDir}:/work`,
3676
3627
  this.IMAGE_NAME,
@@ -3686,7 +3637,8 @@ class DockerRunner {
3686
3637
  }
3687
3638
  args.push(`${specName}.tla`);
3688
3639
  const result = await this.runCommand("docker", args, {
3689
- timeout: options?.timeout
3640
+ timeout: options?.timeout,
3641
+ containerName
3690
3642
  });
3691
3643
  return this.parseTLCOutput(result);
3692
3644
  }
@@ -3769,7 +3721,10 @@ class DockerRunner {
3769
3721
  });
3770
3722
  const timeoutValue = options?.timeout ?? 0;
3771
3723
  const timeout = timeoutValue > 0 ? setTimeout(() => {
3772
- proc.kill();
3724
+ proc.kill("SIGKILL");
3725
+ if (options?.containerName) {
3726
+ removeContainer(options.containerName);
3727
+ }
3773
3728
  reject(new Error(`Command timed out after ${Math.floor(timeoutValue / 1000)}s. TLC was still making progress. Consider increasing the timeout or setting timeout: 0 for no timeout.`));
3774
3729
  }, timeoutValue) : null;
3775
3730
  proc.on("close", (exitCode) => {
@@ -3834,7 +3789,7 @@ class DockerRunner {
3834
3789
  });
3835
3790
  }
3836
3791
  }
3837
- var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src/runner";
3792
+ var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src/runner", swept = false;
3838
3793
  var init_docker = () => {};
3839
3794
 
3840
3795
  // tools/verify/src/cli.ts
@@ -5161,7 +5116,8 @@ import { Project as Project2 } from "ts-morph";
5161
5116
  import {
5162
5117
  Node as Node2,
5163
5118
  Project,
5164
- SyntaxKind
5119
+ SyntaxKind,
5120
+ VariableDeclarationKind
5165
5121
  } from "ts-morph";
5166
5122
 
5167
5123
  // tools/analysis/src/extract/relationships.ts
@@ -6547,16 +6503,116 @@ class HandlerExtractor {
6547
6503
  return;
6548
6504
  }
6549
6505
  if (this.isTranslatableInitializer(initializer)) {
6550
- assignments.push({ field, value: `EXPR:${initializer.getText()}` });
6506
+ const captured = this.resolveSignalAliases(this.unwrapParentheses(initializer));
6507
+ assignments.push({ field, value: `EXPR:${captured}` });
6551
6508
  return;
6552
6509
  }
6553
- if (process.env["POLLY_DEBUG"]) {
6554
- const on = signalName ? ` on ${signalName}.value` : "";
6555
- console.log(`[WARN] dropped object-literal property '${name}'${on} — initializer kind ${initializer.getKindName()} is not translatable to TLA+`);
6556
- }
6510
+ const on = signalName ? ` on ${signalName}.value` : "";
6511
+ console.log(`[WARN] dropped object-literal property '${name}'${on} — initializer \`${initializer.getText().replace(/\s+/g, " ").slice(0, 80)}\` (${initializer.getKindName()}) is not translatable to TLA+`);
6512
+ }
6513
+ resolveSignalAliases(initializer) {
6514
+ const aliases = this.collectSignalAliases(initializer);
6515
+ if (aliases.size === 0)
6516
+ return initializer.getText();
6517
+ const base = initializer.getStart();
6518
+ const edits = [];
6519
+ const visit = (node) => {
6520
+ if (!Node2.isPropertyAccessExpression(node))
6521
+ return;
6522
+ const target = node.getExpression();
6523
+ if (!Node2.isIdentifier(target))
6524
+ return;
6525
+ const signal = aliases.get(target.getText());
6526
+ if (!signal)
6527
+ return;
6528
+ edits.push({
6529
+ start: target.getStart() - base,
6530
+ end: target.getEnd() - base,
6531
+ text: `${signal}.value`
6532
+ });
6533
+ };
6534
+ visit(initializer);
6535
+ initializer.forEachDescendant(visit);
6536
+ if (edits.length === 0)
6537
+ return initializer.getText();
6538
+ let text = initializer.getText();
6539
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
6540
+ text = text.slice(0, edit.start) + edit.text + text.slice(edit.end);
6541
+ }
6542
+ return text;
6543
+ }
6544
+ collectSignalAliases(node) {
6545
+ const aliases = new Map;
6546
+ const fn = node.getFirstAncestor((a) => Node2.isArrowFunction(a) || Node2.isFunctionExpression(a) || Node2.isFunctionDeclaration(a));
6547
+ if (!fn)
6548
+ return aliases;
6549
+ const reassigned = this.collectReassignedIdentifiers(fn);
6550
+ fn.forEachDescendant((inner) => {
6551
+ if (!Node2.isVariableDeclaration(inner))
6552
+ return;
6553
+ const binding = this.readSignalAliasBinding(inner);
6554
+ if (!binding)
6555
+ return;
6556
+ if (aliases.has(binding.signal))
6557
+ return;
6558
+ if (reassigned.has(binding.alias))
6559
+ return;
6560
+ aliases.set(binding.alias, binding.signal);
6561
+ });
6562
+ return aliases;
6563
+ }
6564
+ collectReassignedIdentifiers(fn) {
6565
+ const reassigned = new Set;
6566
+ fn.forEachDescendant((inner) => {
6567
+ if (!Node2.isBinaryExpression(inner))
6568
+ return;
6569
+ if (inner.getOperatorToken().getText() !== "=")
6570
+ return;
6571
+ const left = inner.getLeft();
6572
+ if (Node2.isIdentifier(left))
6573
+ reassigned.add(left.getText());
6574
+ });
6575
+ return reassigned;
6576
+ }
6577
+ readSignalAliasBinding(decl) {
6578
+ const list = decl.getParent();
6579
+ if (!Node2.isVariableDeclarationList(list))
6580
+ return null;
6581
+ if (list.getDeclarationKind() !== VariableDeclarationKind.Const)
6582
+ return null;
6583
+ const nameNode = decl.getNameNode();
6584
+ if (!Node2.isIdentifier(nameNode))
6585
+ return null;
6586
+ const init = decl.getInitializer();
6587
+ if (!init || !Node2.isPropertyAccessExpression(init))
6588
+ return null;
6589
+ if (init.getName() !== "value")
6590
+ return null;
6591
+ const target = init.getExpression();
6592
+ if (!Node2.isIdentifier(target))
6593
+ return null;
6594
+ return { alias: nameNode.getText(), signal: target.getText() };
6557
6595
  }
6558
6596
  isTranslatableInitializer(node) {
6559
- return Node2.isBinaryExpression(node) || Node2.isPropertyAccessExpression(node) || Node2.isElementAccessExpression(node) || Node2.isPrefixUnaryExpression(node) || Node2.isPostfixUnaryExpression(node) || Node2.isParenthesizedExpression(node);
6597
+ const kindAllowed = Node2.isBinaryExpression(node) || Node2.isPropertyAccessExpression(node) || Node2.isElementAccessExpression(node) || Node2.isPrefixUnaryExpression(node) || Node2.isPostfixUnaryExpression(node) || Node2.isParenthesizedExpression(node) || Node2.isConditionalExpression(node);
6598
+ if (!kindAllowed)
6599
+ return false;
6600
+ if (this.containsComment(node))
6601
+ return false;
6602
+ if (node.getFirstDescendantByKind(SyntaxKind.CallExpression))
6603
+ return false;
6604
+ return this.unwrapParentheses(node).getFirstDescendantByKind(SyntaxKind.ConditionalExpression) === undefined;
6605
+ }
6606
+ unwrapParentheses(node) {
6607
+ let current = node;
6608
+ while (Node2.isParenthesizedExpression(current)) {
6609
+ current = current.getExpression();
6610
+ }
6611
+ return current;
6612
+ }
6613
+ containsComment(node) {
6614
+ const text = node.getText();
6615
+ return text.includes("//") || text.includes("/*");
6560
6616
  }
6561
6617
  extractPayloadPropertyParam(initializer) {
6562
6618
  if (!Node2.isPropertyAccessExpression(initializer))
@@ -9584,4 +9640,4 @@ main().catch((error) => {
9584
9640
  process.exit(1);
9585
9641
  });
9586
9642
 
9587
- //# debugId=F55B4EE81F3A37C864756E2164756E21
9643
+ //# debugId=D4D47405397BD18D64756E2164756E21