@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.
- package/dist/gallery/gallery.js +6 -4
- package/dist/src/client/index.js.map +2 -2
- package/dist/src/client/wrapper.d.ts +3 -3
- package/dist/src/elysia/index.d.ts +1 -1
- package/dist/src/elysia/types.d.ts +11 -2
- package/dist/src/peer.js +30 -1
- package/dist/src/peer.js.map +3 -3
- package/dist/src/polly-ui/index.js +5 -3
- package/dist/src/polly-ui/index.js.map +3 -3
- package/dist/src/shared/lib/peer-relay-adapter.d.ts +6 -0
- package/dist/tools/test/src/browser/index.js +15 -4
- package/dist/tools/test/src/browser/index.js.map +3 -3
- package/dist/tools/test/src/browser/run.d.ts +10 -3
- package/dist/tools/test/src/browser/run.js +324 -33
- package/dist/tools/test/src/browser/run.js.map +7 -5
- package/dist/tools/test/src/browser/runner-core.d.ts +64 -0
- package/dist/tools/test/src/browser/source-map.d.ts +31 -0
- package/dist/tools/test/src/e2e-mesh/index.js +43 -7
- package/dist/tools/test/src/e2e-mesh/index.js.map +6 -5
- package/dist/tools/test/src/e2e-mesh/with-relay.d.ts +1 -1
- package/dist/tools/test/src/e2e-relay/index.js.map +1 -1
- package/dist/tools/test/src/e2e-shared/ephemeral-port.d.ts +73 -0
- package/dist/tools/test/src/e2e-shared/index.d.ts +1 -0
- package/dist/tools/test/src/index.d.ts +1 -0
- package/dist/tools/test/src/index.js +43 -1
- package/dist/tools/test/src/index.js.map +4 -3
- package/dist/tools/test/src/tiers/budget.d.ts +32 -0
- package/dist/tools/test/src/tiers/cli.js +35 -5
- package/dist/tools/test/src/tiers/cli.js.map +6 -5
- package/dist/tools/test/src/tiers/index.d.ts +1 -0
- package/dist/tools/test/src/tiers/reporter.d.ts +0 -5
- package/dist/tools/test/src/tiers/types.d.ts +9 -0
- package/dist/tools/verify/specs/README.md +18 -0
- package/dist/tools/verify/specs/tla/MeshState.cfg +1 -1
- package/dist/tools/verify/specs/tla/MeshState.tla +7 -0
- package/dist/tools/verify/specs/tla/MessageRouter.cfg +4 -2
- package/dist/tools/verify/specs/tla/MessageRouter.tla +45 -10
- package/dist/tools/verify/specs/tla/PeerState.cfg +1 -1
- package/dist/tools/verify/specs/tla/PeerState.tla +7 -0
- package/dist/tools/verify/specs/tla/README.md +1 -1
- package/dist/tools/verify/src/cli.js +235 -182
- package/dist/tools/verify/src/cli.js.map +10 -9
- package/dist/tools/verify/src/config.d.ts +25 -0
- package/dist/tools/verify/src/config.js.map +2 -2
- package/dist/tools/visualize/src/cli.js +437 -45
- package/dist/tools/visualize/src/cli.js.map +7 -5
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
664
|
-
lines.push(
|
|
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
|
-
|
|
777
|
-
lines.push(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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("\\*
|
|
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
|
-
|
|
6506
|
+
const captured = this.resolveSignalAliases(this.unwrapParentheses(initializer));
|
|
6507
|
+
assignments.push({ field, value: `EXPR:${captured}` });
|
|
6554
6508
|
return;
|
|
6555
6509
|
}
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
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
|
-
|
|
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=
|
|
9643
|
+
//# debugId=D4D47405397BD18D64756E2164756E21
|