@fairfox/polly 0.85.2 → 0.87.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 (47) hide show
  1. package/dist/gallery/gallery.js +6 -4
  2. package/dist/src/client/index.js.map +2 -2
  3. package/dist/src/client/wrapper.d.ts +3 -3
  4. package/dist/src/elysia/index.d.ts +1 -1
  5. package/dist/src/elysia/types.d.ts +11 -2
  6. package/dist/src/peer.js +30 -1
  7. package/dist/src/peer.js.map +3 -3
  8. package/dist/src/polly-ui/index.js +5 -3
  9. package/dist/src/polly-ui/index.js.map +3 -3
  10. package/dist/src/shared/lib/peer-relay-adapter.d.ts +6 -0
  11. package/dist/tools/test/src/browser/index.js +15 -4
  12. package/dist/tools/test/src/browser/index.js.map +3 -3
  13. package/dist/tools/test/src/browser/run.d.ts +10 -3
  14. package/dist/tools/test/src/browser/run.js +324 -33
  15. package/dist/tools/test/src/browser/run.js.map +7 -5
  16. package/dist/tools/test/src/browser/runner-core.d.ts +64 -0
  17. package/dist/tools/test/src/browser/source-map.d.ts +31 -0
  18. package/dist/tools/test/src/e2e-mesh/index.js +43 -7
  19. package/dist/tools/test/src/e2e-mesh/index.js.map +6 -5
  20. package/dist/tools/test/src/e2e-mesh/with-relay.d.ts +1 -1
  21. package/dist/tools/test/src/e2e-relay/index.js.map +1 -1
  22. package/dist/tools/test/src/e2e-shared/ephemeral-port.d.ts +73 -0
  23. package/dist/tools/test/src/e2e-shared/index.d.ts +1 -0
  24. package/dist/tools/test/src/index.d.ts +1 -0
  25. package/dist/tools/test/src/index.js +43 -1
  26. package/dist/tools/test/src/index.js.map +4 -3
  27. package/dist/tools/test/src/tiers/budget.d.ts +32 -0
  28. package/dist/tools/test/src/tiers/cli.js +35 -5
  29. package/dist/tools/test/src/tiers/cli.js.map +6 -5
  30. package/dist/tools/test/src/tiers/index.d.ts +1 -0
  31. package/dist/tools/test/src/tiers/reporter.d.ts +0 -5
  32. package/dist/tools/test/src/tiers/types.d.ts +9 -0
  33. package/dist/tools/verify/specs/README.md +18 -0
  34. package/dist/tools/verify/specs/tla/MeshState.cfg +1 -1
  35. package/dist/tools/verify/specs/tla/MeshState.tla +7 -0
  36. package/dist/tools/verify/specs/tla/MessageRouter.cfg +4 -2
  37. package/dist/tools/verify/specs/tla/MessageRouter.tla +45 -10
  38. package/dist/tools/verify/specs/tla/PeerState.cfg +1 -1
  39. package/dist/tools/verify/specs/tla/PeerState.tla +7 -0
  40. package/dist/tools/verify/specs/tla/README.md +1 -1
  41. package/dist/tools/verify/src/cli.js +235 -182
  42. package/dist/tools/verify/src/cli.js.map +10 -9
  43. package/dist/tools/verify/src/config.d.ts +25 -0
  44. package/dist/tools/verify/src/config.js.map +2 -2
  45. package/dist/tools/visualize/src/cli.js +437 -45
  46. package/dist/tools/visualize/src/cli.js.map +7 -5
  47. package/package.json +1 -1
@@ -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,6 +689,7 @@ 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 = {};
@@ -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));
@@ -3612,14 +3508,62 @@ var init_witness2 = __esm(() => {
3612
3508
  ];
3613
3509
  });
3614
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
+
3615
3523
  // tools/verify/src/runner/docker.ts
3616
3524
  var exports_docker = {};
3617
3525
  __export(exports_docker, {
3618
- DockerRunner: () => DockerRunner
3526
+ DockerRunner: () => DockerRunner,
3527
+ removeContainer: () => removeContainer,
3528
+ sweepOrphanedContainers: () => sweepOrphanedContainers,
3529
+ sweepOrphanedContainersOnce: () => sweepOrphanedContainersOnce
3619
3530
  });
3620
- import { spawn } from "node:child_process";
3531
+ import { spawn, spawnSync } from "node:child_process";
3621
3532
  import * as fs3 from "node:fs";
3622
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
+ }
3623
3567
 
3624
3568
  class DockerRunner {
3625
3569
  IMAGE_NAME = "polly-tla:latest";
@@ -3671,9 +3615,13 @@ class DockerRunner {
3671
3615
  if (fs3.existsSync(statesDir)) {
3672
3616
  fs3.rmSync(statesDir, { recursive: true, force: true });
3673
3617
  }
3618
+ const containerName = mintContainerName("tlc");
3619
+ sweepOrphanedContainersOnce();
3674
3620
  const args = [
3675
3621
  "run",
3676
3622
  "--rm",
3623
+ "--name",
3624
+ containerName,
3677
3625
  "-v",
3678
3626
  `${specDir}:/work`,
3679
3627
  this.IMAGE_NAME,
@@ -3689,7 +3637,8 @@ class DockerRunner {
3689
3637
  }
3690
3638
  args.push(`${specName}.tla`);
3691
3639
  const result = await this.runCommand("docker", args, {
3692
- timeout: options?.timeout
3640
+ timeout: options?.timeout,
3641
+ containerName
3693
3642
  });
3694
3643
  return this.parseTLCOutput(result);
3695
3644
  }
@@ -3772,7 +3721,10 @@ class DockerRunner {
3772
3721
  });
3773
3722
  const timeoutValue = options?.timeout ?? 0;
3774
3723
  const timeout = timeoutValue > 0 ? setTimeout(() => {
3775
- proc.kill();
3724
+ proc.kill("SIGKILL");
3725
+ if (options?.containerName) {
3726
+ removeContainer(options.containerName);
3727
+ }
3776
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.`));
3777
3729
  }, timeoutValue) : null;
3778
3730
  proc.on("close", (exitCode) => {
@@ -3837,7 +3789,7 @@ class DockerRunner {
3837
3789
  });
3838
3790
  }
3839
3791
  }
3840
- 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;
3841
3793
  var init_docker = () => {};
3842
3794
 
3843
3795
  // tools/verify/src/cli.ts
@@ -5164,7 +5116,8 @@ import { Project as Project2 } from "ts-morph";
5164
5116
  import {
5165
5117
  Node as Node2,
5166
5118
  Project,
5167
- SyntaxKind
5119
+ SyntaxKind,
5120
+ VariableDeclarationKind
5168
5121
  } from "ts-morph";
5169
5122
 
5170
5123
  // tools/analysis/src/extract/relationships.ts
@@ -6550,16 +6503,116 @@ class HandlerExtractor {
6550
6503
  return;
6551
6504
  }
6552
6505
  if (this.isTranslatableInitializer(initializer)) {
6553
- assignments.push({ field, value: `EXPR:${initializer.getText()}` });
6506
+ const captured = this.resolveSignalAliases(this.unwrapParentheses(initializer));
6507
+ assignments.push({ field, value: `EXPR:${captured}` });
6554
6508
  return;
6555
6509
  }
6556
- if (process.env["POLLY_DEBUG"]) {
6557
- const on = signalName ? ` on ${signalName}.value` : "";
6558
- console.log(`[WARN] dropped object-literal property '${name}'${on} — initializer kind ${initializer.getKindName()} is not translatable to TLA+`);
6559
- }
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() };
6560
6595
  }
6561
6596
  isTranslatableInitializer(node) {
6562
- 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("/*");
6563
6616
  }
6564
6617
  extractPayloadPropertyParam(initializer) {
6565
6618
  if (!Node2.isPropertyAccessExpression(initializer))
@@ -9587,4 +9640,4 @@ main().catch((error) => {
9587
9640
  process.exit(1);
9588
9641
  });
9589
9642
 
9590
- //# debugId=82348038946F09C064756E2164756E21
9643
+ //# debugId=D4D47405397BD18D64756E2164756E21