@fairfox/polly 0.71.0 → 0.72.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.
@@ -89,10 +89,12 @@ function isNullable(configEntry) {
89
89
  }
90
90
  return false;
91
91
  }
92
- function checkUnmodeledFields(expression, configKeys, stateConfig, messageType, conditionType, location) {
92
+ function checkUnmodeledFields(expression, configKeys, stateConfig, messageType, conditionType, location, meshSignalNames) {
93
93
  const warnings = [];
94
94
  const refs = extractFieldRefs(expression);
95
95
  for (const ref of refs) {
96
+ if (meshSignalNames.has(ref.split(".")[0] ?? ref))
97
+ continue;
96
98
  if (!fieldInConfig(ref, configKeys, stateConfig)) {
97
99
  warnings.push({
98
100
  kind: "unmodeled_field",
@@ -195,7 +197,7 @@ function checkWeakPostconditions(expression, handler, messageType, conditionType
195
197
  }
196
198
  return [];
197
199
  }
198
- function validateExpressions(handlers, stateConfig) {
200
+ function validateExpressions(handlers, stateConfig, meshSignalNames = new Set) {
199
201
  const warnings = [];
200
202
  let validCount = 0;
201
203
  const configKeys = new Set(Object.keys(stateConfig));
@@ -220,7 +222,7 @@ function validateExpressions(handlers, stateConfig) {
220
222
  };
221
223
  const condWarnings = [];
222
224
  if (!isPayloadOnly) {
223
- condWarnings.push(...checkUnmodeledFields(cond.expression, configKeys, stateConfig, handler.messageType, type, loc));
225
+ condWarnings.push(...checkUnmodeledFields(cond.expression, configKeys, stateConfig, handler.messageType, type, loc, meshSignalNames));
224
226
  }
225
227
  condWarnings.push(...checkUnsupportedMethods(cond.expression, configKeys, stateConfig, handler.messageType, type, loc));
226
228
  condWarnings.push(...checkOptionalChaining(cond.expression, handler.messageType, type, loc));
@@ -1216,13 +1218,15 @@ var init_tla = __esm(() => {
1216
1218
  fieldLines.push(`${this.sanitizeFieldName(fieldName)}: ${tlaType}`);
1217
1219
  }
1218
1220
  this.line(`\\* Document type for ${docId}`);
1219
- this.line(`MeshDoc_${this.sanitizeFieldName(docId)} == [${fieldLines.join(", ")}]`);
1221
+ this.line(`MeshDoc_${this.sanitizeIdentifier(docId)} == [${fieldLines.join(", ")}]`);
1220
1222
  this.line("");
1221
1223
  }
1222
- this.line("\\* Initial mesh-document values (one record per declared docId)");
1223
- this.line("InitialMesh == [");
1224
+ this.line("\\* Initial mesh-document values a function keyed by docId");
1225
+ this.line("InitialMesh ==");
1226
+ this.indent++;
1227
+ this.line("[d \\in MeshDocs |->");
1224
1228
  this.indent++;
1225
- const initLines = [];
1229
+ const caseArms = [];
1226
1230
  for (const docId of docIds) {
1227
1231
  const fields = mesh[docId];
1228
1232
  if (!fields)
@@ -1232,15 +1236,19 @@ var init_tla = __esm(() => {
1232
1236
  const initVal = this.fieldConfigInitialValue(`${docId}_${fieldName}`, fieldConfig, config);
1233
1237
  inner.push(`${this.sanitizeFieldName(fieldName)} |-> ${initVal}`);
1234
1238
  }
1235
- initLines.push(`"${docId}" |-> [${inner.join(", ")}]`);
1239
+ caseArms.push(`d = "${docId}" -> [${inner.join(", ")}]`);
1236
1240
  }
1237
- initLines.forEach((line, i) => {
1238
- this.line(line + (i < initLines.length - 1 ? "," : ""));
1241
+ caseArms.forEach((arm, i) => {
1242
+ const prefix = i === 0 ? "CASE " : " [] ";
1243
+ const suffix = i === caseArms.length - 1 ? "]" : "";
1244
+ this.line(prefix + arm + suffix);
1239
1245
  });
1240
- this.indent--;
1241
- this.line("]");
1246
+ this.indent -= 2;
1242
1247
  this.line("");
1243
1248
  }
1249
+ sanitizeIdentifier(key) {
1250
+ return key.replace(/[^A-Za-z0-9_]/g, "_");
1251
+ }
1244
1252
  fieldConfigInitialValue(_path, fieldConfig, _config) {
1245
1253
  const fc = fieldConfig;
1246
1254
  if (fc["type"] === "boolean")
@@ -2049,7 +2057,7 @@ var init_tla = __esm(() => {
2049
2057
  if (typeof expr !== "string")
2050
2058
  return null;
2051
2059
  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*$/);
2060
+ const match = trimmed.match(/^(forAllPeers|somePeer)\s*\(\s*\(?\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)?\s*=>\s*([\s\S]+)\)\s*$/);
2053
2061
  if (!match)
2054
2062
  return null;
2055
2063
  const fn = match[1];
@@ -3217,6 +3225,42 @@ init_expression_validator();
3217
3225
  import * as fs4 from "node:fs";
3218
3226
  import * as path4 from "node:path";
3219
3227
 
3228
+ // tools/verify/src/analysis/mesh-signal-warnings.ts
3229
+ function computeMeshOrPeerSignalFindings(analysis, declaredMeshDocs) {
3230
+ const signals = analysis.meshOrPeerSignals ?? [];
3231
+ if (signals.length === 0)
3232
+ return [];
3233
+ const unverified = signals.filter((s) => !(s.kind === "mesh" && declaredMeshDocs.has(s.key)));
3234
+ if (unverified.length === 0)
3235
+ return [];
3236
+ const findings = [];
3237
+ for (const handler of analysis.handlers) {
3238
+ const scan = (conditions, kind) => {
3239
+ for (const cond of conditions) {
3240
+ for (const sig of unverified) {
3241
+ const pattern = new RegExp(`\\b${sig.variableName}\\.value\\b`);
3242
+ if (pattern.test(cond.expression)) {
3243
+ findings.push({
3244
+ handler: handler.messageType,
3245
+ kind,
3246
+ expression: cond.expression,
3247
+ signalName: sig.variableName,
3248
+ signalKind: sig.kind,
3249
+ location: {
3250
+ file: handler.location?.file ?? sig.filePath,
3251
+ line: cond.location?.line ?? handler.location?.line ?? sig.line
3252
+ }
3253
+ });
3254
+ }
3255
+ }
3256
+ }
3257
+ };
3258
+ scan(handler.preconditions, "precondition");
3259
+ scan(handler.postconditions, "postcondition");
3260
+ }
3261
+ return findings;
3262
+ }
3263
+
3220
3264
  // tools/verify/src/config/types.ts
3221
3265
  function isAdapterConfig(config) {
3222
3266
  return "adapter" in config;
@@ -7657,7 +7701,8 @@ async function estimateCommand() {
7657
7701
  const analysis = await runCodebaseAnalysis();
7658
7702
  const typedConfig = config;
7659
7703
  const typedAnalysis = analysis;
7660
- const exprValidation = validateExpressions(typedAnalysis.handlers, typedConfig.state);
7704
+ const meshSignalNames = new Set((typedAnalysis.meshOrPeerSignals ?? []).map((s) => s.variableName));
7705
+ const exprValidation = validateExpressions(typedAnalysis.handlers, typedConfig.state, meshSignalNames);
7661
7706
  if (exprValidation.warnings.length > 0) {
7662
7707
  displayExpressionWarnings(exprValidation);
7663
7708
  }
@@ -7744,44 +7789,28 @@ function displayExpressionWarnings(result) {
7744
7789
  console.log();
7745
7790
  }
7746
7791
  }
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
- }
7792
+ function displayMeshOrPeerSignalWarnings(analysis, declaredMeshDocs) {
7793
+ const findings = computeMeshOrPeerSignalFindings(analysis, declaredMeshDocs);
7776
7794
  if (findings.length === 0)
7777
7795
  return;
7796
+ const hasUndeclaredMesh = findings.some((f) => f.signalKind === "mesh");
7797
+ const hasPeer = findings.some((f) => f.signalKind === "peer");
7778
7798
  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));
7799
+ ⚠️ ${findings.length} predicate(s) reference an unverified $meshState/$peerState signal (polly#117):`, COLORS.yellow));
7800
+ console.log(color(" These signals are flattened to single-context local state, so a green run", COLORS.gray));
7801
+ console.log(color(" does NOT prove the claim holds across peers.", COLORS.gray));
7802
+ if (hasUndeclaredMesh) {
7803
+ console.log(color(" Fix ($meshState): declare the document key under `mesh: { ... }` in your", COLORS.gray));
7804
+ console.log(color(" verification config. Declared mesh docs are routed through the meshState", COLORS.gray));
7805
+ console.log(color(" namespace with a PropagateMeshOp action, so `forAllPeers(...)` claims are checked.", COLORS.gray));
7806
+ }
7807
+ if (hasPeer) {
7808
+ console.log(color(" $peerState signals have no cross-peer verification model yet.", COLORS.gray));
7809
+ }
7810
+ console.log();
7783
7811
  for (const f of findings) {
7784
- console.log(color(` ⚠ ${f.handler} ${f.kind}: ${f.signalName} is $${f.signalKind}State, not single-context`, COLORS.yellow));
7812
+ const remedy = f.signalKind === "mesh" ? "not declared in config.mesh" : "$peerState no cross-peer model";
7813
+ console.log(color(` ⚠ ${f.handler} ${f.kind}: ${f.signalName} (${remedy})`, COLORS.yellow));
7785
7814
  console.log(color(` ${f.expression}`, COLORS.gray));
7786
7815
  console.log(color(` at ${f.location.file}:${f.location.line}`, COLORS.gray));
7787
7816
  console.log();
@@ -7851,11 +7880,13 @@ async function runFullVerification(configPath) {
7851
7880
  const analysis = await runCodebaseAnalysis();
7852
7881
  const typedConfig = config;
7853
7882
  const typedAnalysis = analysis;
7854
- const exprValidation = validateExpressions(typedAnalysis.handlers, typedConfig.state);
7883
+ const meshSignalNames = new Set((typedAnalysis.meshOrPeerSignals ?? []).map((s) => s.variableName));
7884
+ const exprValidation = validateExpressions(typedAnalysis.handlers, typedConfig.state, meshSignalNames);
7855
7885
  if (exprValidation.warnings.length > 0) {
7856
7886
  displayExpressionWarnings(exprValidation);
7857
7887
  }
7858
- displayMeshOrPeerSignalWarnings(typedAnalysis);
7888
+ const declaredMeshDocs = new Set(Object.keys(typedConfig.mesh ?? {}));
7889
+ displayMeshOrPeerSignalWarnings(typedAnalysis, declaredMeshDocs);
7859
7890
  if (typedConfig.subsystems && Object.keys(typedConfig.subsystems).length > 0) {
7860
7891
  await runSubsystemVerification(typedConfig, typedAnalysis);
7861
7892
  return;
@@ -8256,4 +8287,4 @@ main().catch((error) => {
8256
8287
  process.exit(1);
8257
8288
  });
8258
8289
 
8259
- //# debugId=E07763C4D9F04C6264756E2164756E21
8290
+ //# debugId=15266168B7768F5064756E2164756E21