@fairfox/polly 0.67.0 → 0.70.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 (35) hide show
  1. package/dist/src/client/index.js +17 -20
  2. package/dist/src/client/index.js.map +4 -4
  3. package/dist/src/mesh.js +85 -11
  4. package/dist/src/mesh.js.map +7 -6
  5. package/dist/src/peer.js +80 -6
  6. package/dist/src/peer.js.map +7 -6
  7. package/dist/src/polly-ui/markdown.js +3 -3
  8. package/dist/src/polly-ui/markdown.js.map +2 -2
  9. package/dist/src/shared/lib/crdt-specialised.d.ts +6 -0
  10. package/dist/src/shared/lib/crdt-state.d.ts +8 -1
  11. package/dist/src/shared/lib/mesh-diagnostics.d.ts +98 -0
  12. package/dist/src/shared/lib/mesh-network-adapter.d.ts +0 -4
  13. package/dist/tools/test/src/e2e-mesh/console-allowlist.d.ts +31 -0
  14. package/dist/tools/test/src/e2e-mesh/index.d.ts +27 -0
  15. package/dist/tools/test/src/e2e-mesh/index.js +1089 -0
  16. package/dist/tools/test/src/e2e-mesh/index.js.map +22 -0
  17. package/dist/tools/test/src/e2e-mesh/keys.d.ts +55 -0
  18. package/dist/tools/test/src/e2e-mesh/launch-peer.d.ts +70 -0
  19. package/dist/tools/test/src/e2e-mesh/mesh-assertions.d.ts +53 -0
  20. package/dist/tools/test/src/e2e-mesh/serve-consumer.d.ts +32 -0
  21. package/dist/tools/test/src/e2e-mesh/wait-for-convergence.d.ts +38 -0
  22. package/dist/tools/test/src/e2e-mesh/with-relay.d.ts +53 -0
  23. package/dist/tools/test/src/visual/index.js +24 -24
  24. package/dist/tools/test/src/visual/index.js.map +2 -2
  25. package/dist/tools/verify/src/cli.js +361 -22
  26. package/dist/tools/verify/src/cli.js.map +6 -6
  27. package/dist/tools/verify/src/config.d.ts +26 -1
  28. package/dist/tools/verify/src/config.js +9 -1
  29. package/dist/tools/verify/src/config.js.map +4 -4
  30. package/dist/tools/verify/src/primitives/index.d.ts +30 -0
  31. package/dist/tools/visualize/src/cli.js +43 -1
  32. package/dist/tools/visualize/src/cli.js.map +3 -3
  33. package/package.json +11 -8
  34. package/LICENSE +0 -21
  35. package/README.md +0 -362
@@ -798,6 +798,8 @@ var init_tla = __esm(() => {
798
798
  tabSymmetryEnabled = false;
799
799
  tabCount = 0;
800
800
  moduleName = "UserApp";
801
+ currentConfig;
802
+ meshSignalDocs = new Map;
801
803
  constructor(options) {
802
804
  this.options = options;
803
805
  }
@@ -811,6 +813,19 @@ var init_tla = __esm(() => {
811
813
  if (moduleName) {
812
814
  this.moduleName = moduleName;
813
815
  }
816
+ this.currentConfig = config;
817
+ this.meshSignalDocs.clear();
818
+ if (this.hasMeshConfig(config)) {
819
+ const declaredDocs = new Set(Object.keys(config.mesh ?? {}));
820
+ const sigs = analysis.meshOrPeerSignals;
821
+ if (sigs) {
822
+ for (const s of sigs) {
823
+ if (declaredDocs.has(s.key)) {
824
+ this.meshSignalDocs.set(s.variableName, s.key);
825
+ }
826
+ }
827
+ }
828
+ }
814
829
  this.validateInputs(config, analysis);
815
830
  this.extractInvariantsIfEnabled();
816
831
  this.generateTemporalPropertiesIfEnabled(analysis);
@@ -963,7 +978,7 @@ var init_tla = __esm(() => {
963
978
  this.addMessageTypes(config, analysis);
964
979
  this.addTabSymmetry(config);
965
980
  this.addStateType(config, analysis);
966
- this.addVariables();
981
+ this.addVariables(config);
967
982
  if (this.temporalProperties.length > 0) {
968
983
  this.addDeliveredTracking();
969
984
  }
@@ -1181,6 +1196,74 @@ var init_tla = __esm(() => {
1181
1196
  this.indent--;
1182
1197
  this.line("]");
1183
1198
  this.line("");
1199
+ this.addMeshTypes(config);
1200
+ }
1201
+ addMeshTypes(config) {
1202
+ const mesh = config.mesh;
1203
+ if (!mesh || Object.keys(mesh).length === 0)
1204
+ return;
1205
+ const docIds = Object.keys(mesh).sort();
1206
+ this.line("\\* polly#117: declared $meshState documents");
1207
+ this.line(`MeshDocs == {${docIds.map((d) => `"${d}"`).join(", ")}}`);
1208
+ this.line("");
1209
+ for (const docId of docIds) {
1210
+ const fields = mesh[docId];
1211
+ if (!fields)
1212
+ continue;
1213
+ const fieldLines = [];
1214
+ for (const [fieldName, fieldConfig] of Object.entries(fields)) {
1215
+ const tlaType = this.fieldConfigToTLAType(`${docId}_${fieldName}`, fieldConfig, config);
1216
+ fieldLines.push(`${this.sanitizeFieldName(fieldName)}: ${tlaType}`);
1217
+ }
1218
+ this.line(`\\* Document type for ${docId}`);
1219
+ this.line(`MeshDoc_${this.sanitizeFieldName(docId)} == [${fieldLines.join(", ")}]`);
1220
+ this.line("");
1221
+ }
1222
+ this.line("\\* Initial mesh-document values (one record per declared docId)");
1223
+ this.line("InitialMesh == [");
1224
+ this.indent++;
1225
+ const initLines = [];
1226
+ for (const docId of docIds) {
1227
+ const fields = mesh[docId];
1228
+ if (!fields)
1229
+ continue;
1230
+ const inner = [];
1231
+ for (const [fieldName, fieldConfig] of Object.entries(fields)) {
1232
+ const initVal = this.fieldConfigInitialValue(`${docId}_${fieldName}`, fieldConfig, config);
1233
+ inner.push(`${this.sanitizeFieldName(fieldName)} |-> ${initVal}`);
1234
+ }
1235
+ initLines.push(`"${docId}" |-> [${inner.join(", ")}]`);
1236
+ }
1237
+ initLines.forEach((line, i) => {
1238
+ this.line(line + (i < initLines.length - 1 ? "," : ""));
1239
+ });
1240
+ this.indent--;
1241
+ this.line("]");
1242
+ this.line("");
1243
+ }
1244
+ fieldConfigInitialValue(_path, fieldConfig, _config) {
1245
+ const fc = fieldConfig;
1246
+ if (fc["type"] === "boolean")
1247
+ return "FALSE";
1248
+ if (fc["type"] === "number") {
1249
+ const min = typeof fc["min"] === "number" ? fc["min"] : 0;
1250
+ return String(min);
1251
+ }
1252
+ if (fc["type"] === "enum" && Array.isArray(fc["values"]) && fc["values"].length > 0) {
1253
+ return `"${String(fc["values"][0])}"`;
1254
+ }
1255
+ if (fc["type"] === "string") {
1256
+ return typeof fc["initial"] === "string" ? `"${fc["initial"]}"` : '""';
1257
+ }
1258
+ if (fc["type"] === "array") {
1259
+ return "<<>>";
1260
+ }
1261
+ if (Array.isArray(fc.values)) {
1262
+ const values = fc.values;
1263
+ if (values.length > 0)
1264
+ return `"${values[0]}"`;
1265
+ }
1266
+ return '"v1"';
1184
1267
  }
1185
1268
  defineValueTypes() {
1186
1269
  this.line("\\* Generic value type for sequences and maps");
@@ -1402,18 +1485,40 @@ var init_tla = __esm(() => {
1402
1485
  console.log(`[INFO] [TLAGenerator] Tab symmetry enabled: ${this.tabCount} tabs as model values`);
1403
1486
  }
1404
1487
  }
1405
- addVariables() {
1488
+ addVariables(config) {
1489
+ const hasMesh = this.hasMeshConfig(config);
1406
1490
  this.line("\\* Application state per context");
1407
1491
  this.line("VARIABLE contextStates");
1408
1492
  this.line("");
1493
+ if (hasMesh) {
1494
+ this.line("\\* polly#117: per-context mesh document replicas. Each context");
1495
+ this.line("\\* carries an independent record per declared $meshState document.");
1496
+ this.line("\\* The PropagateMeshOp action diffuses values between contexts to");
1497
+ this.line("\\* model Automerge sync, and predicate references to mesh-tagged");
1498
+ this.line("\\* signals route through this variable instead of contextStates.");
1499
+ this.line("VARIABLE meshState");
1500
+ this.line("");
1501
+ }
1409
1502
  this.line("\\* Message payload (abstract model - non-deterministically chosen)");
1410
1503
  this.line("\\* In verification, we model payload fields as potentially any valid value");
1411
1504
  this.line("VARIABLE payload");
1412
1505
  this.line("");
1413
1506
  this.line("\\* All variables (extending MessageRouter vars)");
1414
- this.line("allVars == <<ports, messages, pendingRequests, delivered, routingDepth, time, contextStates, payload>>");
1507
+ const allVarsList = hasMesh ? "ports, messages, pendingRequests, delivered, routingDepth, time, contextStates, meshState, payload" : "ports, messages, pendingRequests, delivered, routingDepth, time, contextStates, payload";
1508
+ this.line(`allVars == <<${allVarsList}>>`);
1415
1509
  this.line("");
1416
1510
  }
1511
+ hasMeshConfig(config) {
1512
+ const c = config ?? this.currentConfig;
1513
+ return !!c?.mesh && Object.keys(c.mesh).length > 0;
1514
+ }
1515
+ userStateVars(config) {
1516
+ return this.hasMeshConfig(config) ? ["contextStates", "meshState"] : ["contextStates"];
1517
+ }
1518
+ unchangedUserStates(config, andOthers = []) {
1519
+ const all = [...andOthers, ...this.userStateVars(config)];
1520
+ return all.length === 1 ? `UNCHANGED ${all[0]}` : `UNCHANGED <<${all.join(", ")}>>`;
1521
+ }
1417
1522
  addInit(config, _analysis) {
1418
1523
  this.deriveParamDomains(config, _analysis);
1419
1524
  this.line("\\* Initial application state");
@@ -1434,6 +1539,9 @@ var init_tla = __esm(() => {
1434
1539
  this.indent++;
1435
1540
  this.line("/\\ Init \\* MessageRouter's init");
1436
1541
  this.line("/\\ contextStates = [c \\in Contexts |-> InitialState]");
1542
+ if (this.hasMeshConfig(config)) {
1543
+ this.line("/\\ meshState = [c \\in Contexts |-> InitialMesh]");
1544
+ }
1437
1545
  this.line("/\\ payload \\in PayloadType \\* Non-deterministic initial payload");
1438
1546
  this.indent--;
1439
1547
  this.line("");
@@ -1629,7 +1737,7 @@ var init_tla = __esm(() => {
1629
1737
  this.line("\\* State remains unchanged for all messages");
1630
1738
  this.line("StateTransition(ctx, msgType) ==");
1631
1739
  this.indent++;
1632
- this.line("UNCHANGED contextStates");
1740
+ this.line(this.unchangedUserStates());
1633
1741
  this.indent--;
1634
1742
  this.line("");
1635
1743
  }
@@ -1638,7 +1746,7 @@ var init_tla = __esm(() => {
1638
1746
  this.line("\\* State remains unchanged for all messages");
1639
1747
  this.line("StateTransition(ctx, msgType) ==");
1640
1748
  this.indent++;
1641
- this.line("UNCHANGED contextStates");
1749
+ this.line(this.unchangedUserStates());
1642
1750
  this.indent--;
1643
1751
  this.line("");
1644
1752
  }
@@ -1741,7 +1849,7 @@ var init_tla = __esm(() => {
1741
1849
  recordPostconditionProperty(messageType, actionName, postconditions) {
1742
1850
  if (postconditions.length === 0)
1743
1851
  return;
1744
- const predicateClauses = postconditions.map((pc) => this.tsExpressionToTLA(pc.expression, true)).filter((p) => p && p.length > 0).map((p) => p.replace(/\[ctx\]/g, "[target]"));
1852
+ const predicateClauses = postconditions.map((pc) => this.tsExpressionToTLA(pc.expression, true)).filter((p) => p && p.length > 0).map((p) => p.replace(/\[ctx\]/g, "[target]").replace(/\{ctx\}/g, "{target}"));
1745
1853
  if (predicateClauses.length === 0)
1746
1854
  return;
1747
1855
  const conjunction = predicateClauses.length === 1 ? predicateClauses[0] : predicateClauses.map((c) => ` /\\ ${c}`).join(`
@@ -1782,7 +1890,23 @@ var init_tla = __esm(() => {
1782
1890
  onBuild: "warn",
1783
1891
  onRelease: "warn"
1784
1892
  };
1785
- return this.flattenStateConfig(fakeConfig).has(sanitized);
1893
+ if (this.flattenStateConfig(fakeConfig).has(sanitized))
1894
+ return true;
1895
+ return this.isMeshAssignmentModeled(field);
1896
+ }
1897
+ isMeshAssignmentModeled(field) {
1898
+ const meshHit = this.classifyAssignmentAsMesh(field);
1899
+ if (!meshHit)
1900
+ return false;
1901
+ const mesh = this.currentConfig?.mesh;
1902
+ if (!mesh)
1903
+ return false;
1904
+ const docConfig = mesh[meshHit.docId];
1905
+ if (!docConfig)
1906
+ return false;
1907
+ if (meshHit.innerField === "")
1908
+ return true;
1909
+ return Object.hasOwn(docConfig, meshHit.innerField);
1786
1910
  }
1787
1911
  shouldIncludeAssignment(assignment, state) {
1788
1912
  if (assignment.value !== null)
@@ -1817,7 +1941,7 @@ var init_tla = __esm(() => {
1817
1941
  if (preconditions.length === 0) {
1818
1942
  this.line("\\* No state changes in handler");
1819
1943
  }
1820
- this.line("/\\ UNCHANGED contextStates");
1944
+ this.line(`/\\ ${this.unchangedUserStates()}`);
1821
1945
  return true;
1822
1946
  }
1823
1947
  const ndetAssignments = [];
@@ -1830,15 +1954,67 @@ var init_tla = __esm(() => {
1830
1954
  }
1831
1955
  }
1832
1956
  if (ndetAssignments.length === 0) {
1833
- const exceptClauses = detAssignments.map((a) => {
1957
+ const partition = this.partitionAssignments(detAssignments);
1958
+ this.emitDeterministicAssignmentPartitions(partition);
1959
+ return false;
1960
+ }
1961
+ this.emitNDETStateUpdates(ndetAssignments, detAssignments);
1962
+ return false;
1963
+ }
1964
+ partitionAssignments(assignments) {
1965
+ const local = [];
1966
+ const mesh = new Map;
1967
+ for (const a of assignments) {
1968
+ const meshHit = this.classifyAssignmentAsMesh(a.field);
1969
+ if (meshHit) {
1970
+ const list = mesh.get(meshHit.docId) ?? [];
1971
+ list.push({ inner: meshHit.innerField, value: a.value });
1972
+ mesh.set(meshHit.docId, list);
1973
+ } else {
1974
+ local.push(a);
1975
+ }
1976
+ }
1977
+ return { local, mesh };
1978
+ }
1979
+ classifyAssignmentAsMesh(field) {
1980
+ for (const [signalName, docId] of this.meshSignalDocs.entries()) {
1981
+ if (field === signalName) {
1982
+ return { docId, innerField: "" };
1983
+ }
1984
+ const prefix = `${signalName}_`;
1985
+ if (field.startsWith(prefix)) {
1986
+ return { docId, innerField: field.substring(prefix.length) };
1987
+ }
1988
+ }
1989
+ return null;
1990
+ }
1991
+ emitDeterministicAssignmentPartitions(partition) {
1992
+ const { local, mesh } = partition;
1993
+ if (local.length > 0) {
1994
+ const exceptClauses = local.map((a) => {
1834
1995
  const tlaValue = this.assignmentValueToTLA(a.value);
1835
1996
  return `![ctx].${this.sanitizeFieldName(a.field)} = ${tlaValue}`;
1836
1997
  });
1837
1998
  this.line(`/\\ contextStates' = [contextStates EXCEPT ${exceptClauses.join(", ")}]`);
1838
- return false;
1999
+ } else if (mesh.size > 0) {
2000
+ this.line("/\\ UNCHANGED contextStates");
2001
+ }
2002
+ if (mesh.size > 0) {
2003
+ const clauses = [];
2004
+ for (const [docId, writes] of mesh.entries()) {
2005
+ for (const w of writes) {
2006
+ const tlaValue = this.assignmentValueToTLA(w.value);
2007
+ if (w.inner === "") {
2008
+ clauses.push(`![ctx]["${docId}"] = ${tlaValue}`);
2009
+ } else {
2010
+ clauses.push(`![ctx]["${docId}"].${this.sanitizeFieldName(w.inner)} = ${tlaValue}`);
2011
+ }
2012
+ }
2013
+ }
2014
+ this.line(`/\\ meshState' = [meshState EXCEPT ${clauses.join(", ")}]`);
2015
+ } else if (this.hasMeshConfig() && local.length > 0) {
2016
+ this.line("/\\ UNCHANGED meshState");
1839
2017
  }
1840
- this.emitNDETStateUpdates(ndetAssignments, detAssignments);
1841
- return false;
1842
2018
  }
1843
2019
  emitNDETStateUpdates(ndetAssignments, detAssignments) {
1844
2020
  const detExceptClauses = detAssignments.map((a) => {
@@ -1869,10 +2045,56 @@ var init_tla = __esm(() => {
1869
2045
  this.indent--;
1870
2046
  }
1871
2047
  }
2048
+ tryExtractPeerScopedPredicate(expr) {
2049
+ if (typeof expr !== "string")
2050
+ return null;
2051
+ const trimmed = expr.trim();
2052
+ const match = trimmed.match(/^(forAllPeers|somePeer)\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=>\s*([\s\S]+)\)\s*$/);
2053
+ if (!match)
2054
+ return null;
2055
+ const fn = match[1];
2056
+ const binder = match[2];
2057
+ const innerBody = match[3];
2058
+ if (!fn || !binder || innerBody === undefined)
2059
+ return null;
2060
+ return {
2061
+ binder,
2062
+ innerBody: innerBody.trim(),
2063
+ kind: fn === "forAllPeers" ? "forall" : "exists"
2064
+ };
2065
+ }
1872
2066
  tsExpressionToTLA(expr, isPrimed = false) {
1873
2067
  if (!expr || typeof expr !== "string") {
1874
2068
  return expr || "";
1875
2069
  }
2070
+ const scoped = this.tryExtractPeerScopedPredicate(expr);
2071
+ if (scoped) {
2072
+ const binder = scoped.binder;
2073
+ const peerPrefix = isPrimed ? `contextStates'[${binder}]` : `contextStates[${binder}]`;
2074
+ let body = scoped.innerBody;
2075
+ const binderRe1 = new RegExp(`\\b${binder}\\.([a-zA-Z_][a-zA-Z0-9_]*)\\.value\\.([a-zA-Z_][a-zA-Z0-9_.]*)`, "g");
2076
+ body = body.replace(binderRe1, (_m, sig, path3) => {
2077
+ const meshDoc = this.meshSignalDocs.get(sig);
2078
+ if (meshDoc !== undefined) {
2079
+ const meshPeerPrefix = isPrimed ? `meshState'[${binder}]["${meshDoc}"]` : `meshState[${binder}]["${meshDoc}"]`;
2080
+ const field = this.sanitizeFieldName(String(path3).replace(/\./g, "_"));
2081
+ return `${meshPeerPrefix}.${field}`;
2082
+ }
2083
+ const flat = this.sanitizeFieldName(`${sig}_${String(path3).replace(/\./g, "_")}`);
2084
+ return `${peerPrefix}.${flat}`;
2085
+ });
2086
+ const binderRe2 = new RegExp(`\\b${binder}\\.([a-zA-Z_][a-zA-Z0-9_]*)\\.value\\b(?!\\.)`, "g");
2087
+ body = body.replace(binderRe2, (_m, sig) => {
2088
+ const meshDoc = this.meshSignalDocs.get(sig);
2089
+ if (meshDoc !== undefined) {
2090
+ return isPrimed ? `meshState'[${binder}]["${meshDoc}"]` : `meshState[${binder}]["${meshDoc}"]`;
2091
+ }
2092
+ return `${peerPrefix}.${sig}`;
2093
+ });
2094
+ const innerTLA = this.tsExpressionToTLA(body, isPrimed);
2095
+ const quantifier = scoped.kind === "forall" ? "\\A" : "\\E";
2096
+ return `${quantifier} ${binder} \\in Contexts \\ {ctx} : (${innerTLA})`;
2097
+ }
1876
2098
  let tla = expr;
1877
2099
  const statePrefix = isPrimed ? "contextStates'[ctx]" : "contextStates[ctx]";
1878
2100
  tla = this.translateComplexExpressions(tla, statePrefix);
@@ -1889,10 +2111,20 @@ var init_tla = __esm(() => {
1889
2111
  }
1890
2112
  tla = tla.replace(/'([^']+)'/g, '"$1"');
1891
2113
  tla = tla.replace(/([a-zA-Z_][a-zA-Z0-9_]*)\.value\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, stateName, path3) => {
2114
+ const meshDoc = this.meshSignalDocs.get(stateName);
2115
+ if (meshDoc !== undefined) {
2116
+ const meshPrefix = isPrimed ? `meshState'[ctx]["${meshDoc}"]` : `meshState[ctx]["${meshDoc}"]`;
2117
+ const field = this.sanitizeFieldName(String(path3).replace(/\./g, "_"));
2118
+ return `${meshPrefix}.${field}`;
2119
+ }
1892
2120
  const fullPath = `${stateName}_${path3.replace(/\./g, "_")}`;
1893
2121
  return `${statePrefix}.${this.sanitizeFieldName(fullPath)}`;
1894
2122
  });
1895
2123
  tla = tla.replace(/([a-zA-Z_][a-zA-Z0-9_]*)\.value\b(?!\.)/g, (_match, stateName) => {
2124
+ const meshDoc = this.meshSignalDocs.get(stateName);
2125
+ if (meshDoc !== undefined) {
2126
+ return isPrimed ? `meshState'[ctx]["${meshDoc}"]` : `meshState[ctx]["${meshDoc}"]`;
2127
+ }
1896
2128
  return `${statePrefix}.${stateName}`;
1897
2129
  });
1898
2130
  tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path3) => {
@@ -2353,28 +2585,48 @@ var init_tla = __esm(() => {
2353
2585
  this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
2354
2586
  this.line(" pendingRequests[id]]");
2355
2587
  this.line(" /\\ time' = time + 1");
2356
- this.line(" /\\ UNCHANGED <<delivered, contextStates, payload>>");
2588
+ this.line(` /\\ ${this.unchangedUserStates(undefined, ["delivered", "payload"])}`);
2357
2589
  this.line(" /\\ UNCHANGED ports");
2358
2590
  this.indent--;
2359
2591
  this.line("");
2360
2592
  }
2593
+ addPropagateMeshOp() {
2594
+ if (!this.hasMeshConfig())
2595
+ return;
2596
+ this.line("\\* polly#117: propagate a mesh-document value from one context to another.");
2597
+ this.line("\\* Models Automerge sync: src and dst must currently disagree on docId, and");
2598
+ this.line("\\* the action copies src's value into dst's record. All other state is fixed.");
2599
+ this.line("PropagateMeshOp(src, dst, docId) ==");
2600
+ this.indent++;
2601
+ this.line("/\\ src # dst");
2602
+ this.line("/\\ meshState[src][docId] # meshState[dst][docId]");
2603
+ this.line("/\\ meshState' = [meshState EXCEPT ![dst][docId] = meshState[src][docId]]");
2604
+ this.line("/\\ UNCHANGED <<ports, messages, pendingRequests, delivered, routingDepth, time, contextStates, payload>>");
2605
+ this.indent--;
2606
+ this.line("");
2607
+ }
2361
2608
  addNext(_config, analysis) {
2609
+ this.addPropagateMeshOp();
2362
2610
  this.line("\\* Next state relation (extends MessageRouter)");
2363
2611
  this.line("UserNext ==");
2364
2612
  this.indent++;
2365
2613
  const hasValidHandlers = analysis.handlers.some((h) => this.isValidTLAIdentifier(h.messageType));
2366
2614
  if (hasValidHandlers) {
2367
- this.line("\\/ \\E c \\in Contexts : ConnectPort(c) /\\ UNCHANGED <<contextStates, payload>>");
2368
- this.line("\\/ \\E c \\in Contexts : DisconnectPort(c) /\\ UNCHANGED <<contextStates, payload>>");
2615
+ const userPayload = this.unchangedUserStates(undefined, ["payload"]);
2616
+ this.line(`\\/ \\E c \\in Contexts : ConnectPort(c) /\\ ${userPayload}`);
2617
+ this.line(`\\/ \\E c \\in Contexts : DisconnectPort(c) /\\ ${userPayload}`);
2369
2618
  this.line("\\/ \\E src \\in Contexts : \\E targetSet \\in (SUBSET Contexts \\ {{}}) : \\E tab \\in Tabs : \\E msgType \\in UserMessageTypes :");
2370
2619
  this.indent++;
2371
- this.line("SendMessage(src, targetSet, tab, msgType) /\\ UNCHANGED <<contextStates, payload>>");
2620
+ this.line(`SendMessage(src, targetSet, tab, msgType) /\\ ${userPayload}`);
2372
2621
  this.indent--;
2373
2622
  this.line("\\/ \\E i \\in 1..Len(messages) : UserRouteMessage(i)");
2374
- this.line("\\/ CompleteRouting /\\ UNCHANGED <<contextStates, payload>>");
2375
- this.line("\\/ \\E i \\in 1..Len(messages) : TimeoutMessage(i) /\\ UNCHANGED <<contextStates, payload>>");
2623
+ this.line(`\\/ CompleteRouting /\\ ${userPayload}`);
2624
+ this.line(`\\/ \\E i \\in 1..Len(messages) : TimeoutMessage(i) /\\ ${userPayload}`);
2625
+ if (this.hasMeshConfig()) {
2626
+ this.line("\\/ \\E src, dst \\in Contexts : \\E docId \\in MeshDocs : PropagateMeshOp(src, dst, docId)");
2627
+ }
2376
2628
  } else {
2377
- this.line("\\/ Next /\\ UNCHANGED <<contextStates, payload>>");
2629
+ this.line(`\\/ Next /\\ ${this.unchangedUserStates(undefined, ["payload"])}`);
2378
2630
  }
2379
2631
  this.indent--;
2380
2632
  this.line("");
@@ -2957,7 +3209,7 @@ class DockerRunner {
2957
3209
  });
2958
3210
  }
2959
3211
  }
2960
- var __dirname = "/Users/AJT/projects/polly/tools/verify/src/runner";
3212
+ var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src/runner";
2961
3213
  var init_docker = () => {};
2962
3214
 
2963
3215
  // tools/verify/src/cli.ts
@@ -4629,6 +4881,7 @@ class HandlerExtractor {
4629
4881
  const stateConstraints = [];
4630
4882
  const globalStateConstraints = [];
4631
4883
  const verifiedStates = [];
4884
+ const meshOrPeerSignals = [];
4632
4885
  const resources = [];
4633
4886
  this.warnings = [];
4634
4887
  const allSourceFiles = this.project.getSourceFiles();
@@ -4659,6 +4912,12 @@ class HandlerExtractor {
4659
4912
  }
4660
4913
  }
4661
4914
  }
4915
+ for (const filePath of this.analyzedFiles) {
4916
+ const sourceFile = this.project.getSourceFile(filePath);
4917
+ if (!sourceFile)
4918
+ continue;
4919
+ meshOrPeerSignals.push(...this.extractMeshOrPeerSignalsFromFile(sourceFile));
4920
+ }
4662
4921
  this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
4663
4922
  this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
4664
4923
  return {
@@ -4667,10 +4926,45 @@ class HandlerExtractor {
4667
4926
  stateConstraints,
4668
4927
  globalStateConstraints,
4669
4928
  verifiedStates,
4929
+ meshOrPeerSignals,
4670
4930
  resources,
4671
4931
  warnings: this.warnings
4672
4932
  };
4673
4933
  }
4934
+ extractMeshOrPeerSignalsFromFile(sourceFile) {
4935
+ const out = [];
4936
+ const filePath = sourceFile.getFilePath();
4937
+ sourceFile.forEachDescendant((node) => {
4938
+ const info = this.recognizeMeshOrPeerStateCall(node, filePath);
4939
+ if (info)
4940
+ out.push(info);
4941
+ });
4942
+ return out;
4943
+ }
4944
+ recognizeMeshOrPeerStateCall(node, filePath) {
4945
+ if (!Node2.isCallExpression(node))
4946
+ return null;
4947
+ const expression = node.getExpression();
4948
+ if (!Node2.isIdentifier(expression))
4949
+ return null;
4950
+ const funcName = expression.getText();
4951
+ const kind = funcName === "$meshState" ? "mesh" : funcName === "$peerState" ? "peer" : null;
4952
+ if (!kind)
4953
+ return null;
4954
+ const args = node.getArguments();
4955
+ const keyArg = args[0];
4956
+ if (!keyArg || !Node2.isStringLiteral(keyArg))
4957
+ return null;
4958
+ const key = keyArg.getLiteralValue();
4959
+ const variableName = this.getVariableNameFromParent(node) || key;
4960
+ return {
4961
+ kind,
4962
+ key,
4963
+ variableName,
4964
+ filePath,
4965
+ line: node.getStartLineNumber()
4966
+ };
4967
+ }
4674
4968
  analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates, resources) {
4675
4969
  const filePath = sourceFile.getFilePath();
4676
4970
  if (this.analyzedFiles.has(filePath)) {
@@ -6790,6 +7084,7 @@ class TypeExtractor {
6790
7084
  stateConstraints: handlerAnalysis.stateConstraints,
6791
7085
  globalStateConstraints: handlerAnalysis.globalStateConstraints,
6792
7086
  verifiedStates: handlerAnalysis.verifiedStates,
7087
+ meshOrPeerSignals: handlerAnalysis.meshOrPeerSignals,
6793
7088
  resources: handlerAnalysis.resources
6794
7089
  };
6795
7090
  }
@@ -7197,7 +7492,7 @@ async function analyzeCodebase(options) {
7197
7492
  return extractor.analyzeCodebase(options.stateFilePath);
7198
7493
  }
7199
7494
  // tools/verify/src/cli.ts
7200
- var __dirname = "/Users/AJT/projects/polly/tools/verify/src";
7495
+ var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src";
7201
7496
  var COLORS = {
7202
7497
  reset: "\x1B[0m",
7203
7498
  red: "\x1B[31m",
@@ -7449,6 +7744,49 @@ function displayExpressionWarnings(result) {
7449
7744
  console.log();
7450
7745
  }
7451
7746
  }
7747
+ function displayMeshOrPeerSignalWarnings(analysis) {
7748
+ const signals = analysis.meshOrPeerSignals ?? [];
7749
+ if (signals.length === 0)
7750
+ return;
7751
+ const findings = [];
7752
+ for (const handler of analysis.handlers) {
7753
+ const scanConditions = (conditions, kind) => {
7754
+ for (const cond of conditions) {
7755
+ for (const sig of signals) {
7756
+ const pattern = new RegExp(`\\b${sig.variableName}\\.value\\b`);
7757
+ if (pattern.test(cond.expression)) {
7758
+ findings.push({
7759
+ handler: handler.messageType,
7760
+ kind,
7761
+ expression: cond.expression,
7762
+ signalName: sig.variableName,
7763
+ signalKind: sig.kind,
7764
+ location: {
7765
+ file: handler.location.file,
7766
+ line: cond.location?.line ?? handler.location.line
7767
+ }
7768
+ });
7769
+ }
7770
+ }
7771
+ }
7772
+ };
7773
+ scanConditions(handler.preconditions, "precondition");
7774
+ scanConditions(handler.postconditions, "postcondition");
7775
+ }
7776
+ if (findings.length === 0)
7777
+ return;
7778
+ console.log(color(`
7779
+ ⚠️ ${findings.length} predicate(s) reference a $meshState/$peerState signal (polly#117):`, COLORS.yellow));
7780
+ console.log(color(" The verifier currently treats these the same as $sharedState — single-context local state.", COLORS.gray));
7781
+ console.log(color(` Cross-peer semantics are NOT verified. A green run does not prove the claim across peers.
7782
+ `, COLORS.gray));
7783
+ for (const f of findings) {
7784
+ console.log(color(` ⚠ ${f.handler} ${f.kind}: ${f.signalName} is $${f.signalKind}State, not single-context`, COLORS.yellow));
7785
+ console.log(color(` ${f.expression}`, COLORS.gray));
7786
+ console.log(color(` at ${f.location.file}:${f.location.line}`, COLORS.gray));
7787
+ console.log();
7788
+ }
7789
+ }
7452
7790
  async function verifyCommand() {
7453
7791
  const configPath = path4.join(process.cwd(), "specs", "verification.config.ts");
7454
7792
  console.log(color(`
@@ -7517,6 +7855,7 @@ async function runFullVerification(configPath) {
7517
7855
  if (exprValidation.warnings.length > 0) {
7518
7856
  displayExpressionWarnings(exprValidation);
7519
7857
  }
7858
+ displayMeshOrPeerSignalWarnings(typedAnalysis);
7520
7859
  if (typedConfig.subsystems && Object.keys(typedConfig.subsystems).length > 0) {
7521
7860
  await runSubsystemVerification(typedConfig, typedAnalysis);
7522
7861
  return;
@@ -7917,4 +8256,4 @@ main().catch((error) => {
7917
8256
  process.exit(1);
7918
8257
  });
7919
8258
 
7920
- //# debugId=E1FA1E90DBF1B2F664756E2164756E21
8259
+ //# debugId=E07763C4D9F04C6264756E2164756E21