@fairfox/polly 0.34.0 → 0.36.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.
@@ -40,6 +40,14 @@ ContextType == {"background", "content", "popup", "devtools", "options", "offscr
40
40
  PortState == {"connected", "disconnected"}
41
41
  MessageStatus == {"pending", "routing", "delivered", "failed", "timeout"}
42
42
 
43
+ \* Sentinel value for messages that have not been routed to a concrete
44
+ \* target yet. Once RouteMessage selects a target from msg.targets, that
45
+ \* target is recorded in deliveredTo so step-temporal postcondition
46
+ \* properties can identify which single target was actually mutated.
47
+ \* Without this field a property that quantifies over msg.targets
48
+ \* false-positives whenever routing picks one target out of many.
49
+ NoTarget == "NoTarget"
50
+
43
51
  Message == [
44
52
  id: Nat,
45
53
  source: ContextType,
@@ -47,7 +55,8 @@ Message == [
47
55
  tabId: Nat,
48
56
  msgType: STRING, \* Message type for handler dispatch
49
57
  status: MessageStatus,
50
- timestamp: Nat
58
+ timestamp: Nat,
59
+ deliveredTo: ContextType \cup {NoTarget}
51
60
  ]
52
61
 
53
62
  -----------------------------------------------------------------------------
@@ -97,7 +106,8 @@ SendMessage(source, targetSet, tabId, messageType) ==
97
106
  tabId |-> tabId,
98
107
  msgType |-> messageType,
99
108
  status |-> "pending",
100
- timestamp |-> time
109
+ timestamp |-> time,
110
+ deliveredTo |-> NoTarget
101
111
  ]
102
112
  IN /\ messages' = Append(messages, newMsg)
103
113
  /\ pendingRequests' = pendingRequests @@
@@ -118,7 +128,8 @@ RouteMessage(msgIndex) ==
118
128
  \E target \in msg.targets :
119
129
  /\ IF target \in Contexts /\ ports[target] = "connected"
120
130
  THEN \* Successful delivery to this target
121
- /\ messages' = [messages EXCEPT ![msgIndex].status = "delivered"]
131
+ /\ messages' = [messages EXCEPT ![msgIndex].status = "delivered",
132
+ ![msgIndex].deliveredTo = target]
122
133
  /\ delivered' = delivered \union {msg.id}
123
134
  /\ pendingRequests' = [id \in DOMAIN pendingRequests \ {msg.id} |->
124
135
  pendingRequests[id]]
@@ -339,6 +339,9 @@ class TemporalTLAGenerator {
339
339
  case "always":
340
340
  lines.push(`${prop.name} == [](${prop.target})`);
341
341
  break;
342
+ case "step-always":
343
+ lines.push(`${prop.name} == [][${prop.target}]_allVars`);
344
+ break;
342
345
  case "until":
343
346
  if (prop.trigger) {
344
347
  lines.push(`${prop.name} == (${prop.trigger}) U (${prop.target})`);
@@ -651,6 +654,7 @@ var init_tla = __esm(() => {
651
654
  if (this.temporalProperties.length > 0) {
652
655
  this.addTemporalProperties();
653
656
  }
657
+ this.line("=============================================================================");
654
658
  return this.lines.join(`
655
659
  `);
656
660
  }
@@ -708,7 +712,7 @@ var init_tla = __esm(() => {
708
712
  }
709
713
  const tabValues = Array.from({ length: this.tabCount }, (_, i) => `Tab${i}`).join(", ");
710
714
  lines.push(` Tabs = {${tabValues}}`);
711
- } else if ("maxTabs" in messages && messages.maxTabs !== undefined) {
715
+ } else if ("maxTabs" in messages && messages.maxTabs !== undefined && messages.maxTabs !== null) {
712
716
  const tabValues = Array.from({ length: messages.maxTabs + 1 }, (_, i) => i).join(", ");
713
717
  lines.push(` Tabs = {${tabValues}}`);
714
718
  } else if (hasProjectConstant) {
@@ -804,7 +808,7 @@ var init_tla = __esm(() => {
804
808
  if (hasStateConstants) {
805
809
  this.generateConstantDeclarations(config.state, false);
806
810
  }
807
- if (hasPerMessageBounds) {
811
+ if (hasPerMessageBounds && config.messages.perMessageBounds) {
808
812
  for (const [msgType, _bound] of Object.entries(config.messages.perMessageBounds)) {
809
813
  const constName = `MaxMessages_${msgType}`;
810
814
  this.line(`,${constName}`);
@@ -831,15 +835,15 @@ var init_tla = __esm(() => {
831
835
  }
832
836
  addFieldConstants(field, fieldConfig, constName, first) {
833
837
  let isFirst = first;
834
- if ("maxLength" in fieldConfig && fieldConfig.maxLength !== null) {
838
+ if ("maxLength" in fieldConfig && fieldConfig["maxLength"] !== null) {
835
839
  this.line(`${isFirst ? "" : ","}${constName}_MaxLength \\* Max length for ${field}`);
836
840
  isFirst = false;
837
841
  }
838
- if ("max" in fieldConfig && fieldConfig.max !== null) {
842
+ if ("max" in fieldConfig && fieldConfig["max"] !== null) {
839
843
  this.line(`${isFirst ? "" : ","}${constName}_Max \\* Max value for ${field}`);
840
844
  isFirst = false;
841
845
  }
842
- if ("maxSize" in fieldConfig && fieldConfig.maxSize !== null) {
846
+ if ("maxSize" in fieldConfig && fieldConfig["maxSize"] !== null) {
843
847
  this.line(`${isFirst ? "" : ","}${constName}_MaxSize \\* Max size for ${field}`);
844
848
  isFirst = false;
845
849
  }
@@ -998,7 +1002,6 @@ var init_tla = __esm(() => {
998
1002
  const messageTypeSet = validMessageTypes.map((t) => `"${t}"`).join(", ");
999
1003
  this.line(`UserMessageTypes == {${messageTypeSet}}`);
1000
1004
  this.line("");
1001
- this.filteredMessageTypes = validMessageTypes;
1002
1005
  if (config.messages.symmetry && config.messages.symmetry.length > 0) {
1003
1006
  this.addSymmetrySets(config.messages.symmetry, validMessageTypes);
1004
1007
  }
@@ -1025,6 +1028,8 @@ var init_tla = __esm(() => {
1025
1028
  }
1026
1029
  for (let i = 0;i < validSymmetryGroups.length; i++) {
1027
1030
  const group = validSymmetryGroups[i];
1031
+ if (!group)
1032
+ continue;
1028
1033
  const setName = `SymmetrySet${i + 1}`;
1029
1034
  const setValues = group.map((t) => `"${t}"`).join(", ");
1030
1035
  this.line(`${setName} == {${setValues}}`);
@@ -1038,7 +1043,8 @@ var init_tla = __esm(() => {
1038
1043
  console.log(`[INFO] [TLAGenerator] Symmetry reduction: ${validSymmetryGroups.length} independent symmetry groups ` + `(${validSymmetryGroups.map((g) => g.length).join(", ")} message types)`);
1039
1044
  } else {
1040
1045
  this.line(`Symmetry == Permutations(SymmetrySet1)`);
1041
- console.log(`[INFO] [TLAGenerator] Symmetry reduction: 1 symmetry group with ${validSymmetryGroups[0].length} message types`);
1046
+ const onlyGroup = validSymmetryGroups[0];
1047
+ console.log(`[INFO] [TLAGenerator] Symmetry reduction: 1 symmetry group with ${onlyGroup?.length ?? 0} message types`);
1042
1048
  }
1043
1049
  this.symmetrySets = ["Symmetry"];
1044
1050
  this.line("");
@@ -1221,11 +1227,12 @@ var init_tla = __esm(() => {
1221
1227
  addPayloadRefsFromHandler(handler, fields) {
1222
1228
  for (const text of this.collectHandlerTexts(handler)) {
1223
1229
  const numMatch = TLAGenerator.NUMERIC_PAYLOAD_PATTERN.exec(text);
1224
- if (numMatch)
1230
+ if (numMatch?.[1])
1225
1231
  fields.set(numMatch[1], "0..2");
1226
1232
  for (const m of text.matchAll(TLAGenerator.PAYLOAD_REF_PATTERN)) {
1227
- if (!fields.has(m[1]))
1228
- fields.set(m[1], TLAGenerator.inferFieldType(m[1]));
1233
+ const name = m[1];
1234
+ if (name && !fields.has(name))
1235
+ fields.set(name, TLAGenerator.inferFieldType(name));
1229
1236
  }
1230
1237
  }
1231
1238
  }
@@ -1404,12 +1411,36 @@ var init_tla = __esm(() => {
1404
1411
  }
1405
1412
  this.emitPreconditions(allPreconditions);
1406
1413
  const validAssignments = this.processAssignments(allAssignments, config.state);
1407
- const usedUnchanged = this.emitStateUpdates(validAssignments, allPreconditions);
1408
- if (!usedUnchanged) {
1409
- this.emitPostconditions(allPostconditions);
1410
- }
1414
+ this.emitStateUpdates(validAssignments, allPreconditions);
1411
1415
  this.indent--;
1412
1416
  this.line("");
1417
+ this.recordPostconditionProperty(messageType, actionName, allPostconditions);
1418
+ }
1419
+ recordPostconditionProperty(messageType, actionName, postconditions) {
1420
+ if (postconditions.length === 0)
1421
+ return;
1422
+ const predicateClauses = postconditions.map((pc) => this.tsExpressionToTLA(pc.expression, true)).filter((p) => p && p.length > 0).map((p) => p.replace(/\[ctx\]/g, "[target]"));
1423
+ if (predicateClauses.length === 0)
1424
+ return;
1425
+ const conjunction = predicateClauses.length === 1 ? predicateClauses[0] : predicateClauses.map((c) => ` /\\ ${c}`).join(`
1426
+ `);
1427
+ const propertyName = `EnsuresAfter_${actionName}`;
1428
+ const messages = postconditions.map((pc) => pc.message).filter((m) => Boolean(m)).join("; ");
1429
+ const target = `
1430
+ \\A m \\in 1..Len(messages) :
1431
+ ` + ` (messages[m].status = "pending"
1432
+ ` + ` /\\ messages'[m].status = "delivered"
1433
+ ` + ` /\\ messages[m].msgType = "${messageType}")
1434
+ ` + ` => LET target == messages'[m].deliveredTo IN
1435
+ ` + ` (target \\in Contexts /\\ ports[target] = "connected")
1436
+ ` + ` =>
1437
+ ` + (predicateClauses.length === 1 ? ` ${predicateClauses[0]}` : conjunction);
1438
+ this.temporalProperties.push({
1439
+ name: propertyName,
1440
+ description: messages.length > 0 ? `ensures(...) for ${messageType}: ${messages}` : `ensures(...) for ${messageType}`,
1441
+ type: "step-always",
1442
+ target
1443
+ });
1413
1444
  }
1414
1445
  emitPreconditions(preconditions) {
1415
1446
  for (const precondition of preconditions) {
@@ -1418,14 +1449,6 @@ var init_tla = __esm(() => {
1418
1449
  this.line(`/\\ ${tlaExpr}${comment}`);
1419
1450
  }
1420
1451
  }
1421
- emitPostconditions(postconditions) {
1422
- for (const postcondition of postconditions) {
1423
- const tlaExpr = this.tsExpressionToTLA(postcondition.expression, true);
1424
- const message = postcondition.message ?? "ensures failed";
1425
- const escapedMessage = message.replace(/"/g, "\\\"");
1426
- this.line(`/\\ Assert(${tlaExpr}, "${escapedMessage}")`);
1427
- }
1428
- }
1429
1452
  processAssignments(assignments, state) {
1430
1453
  return assignments.filter((a) => this.isFieldModeled(a.field, state)).filter((a) => this.shouldIncludeAssignment(a, state)).map((a) => this.mapNullAssignment(a, state));
1431
1454
  }
@@ -1462,7 +1485,7 @@ var init_tla = __esm(() => {
1462
1485
  if ("nullable" in fieldConfig && fieldConfig.nullable)
1463
1486
  return assignment;
1464
1487
  if ("values" in fieldConfig && fieldConfig.values) {
1465
- const nullValue = fieldConfig.values[fieldConfig.values.length - 1];
1488
+ const nullValue = fieldConfig.values[fieldConfig.values.length - 1] ?? null;
1466
1489
  return { ...assignment, value: nullValue };
1467
1490
  }
1468
1491
  return assignment;
@@ -1673,7 +1696,6 @@ var init_tla = __esm(() => {
1673
1696
  result = result.replace(/(\w+(?:\.\w+)*)\.includes\(([^)]+)\)/g, (_match, arrayRef, item) => {
1674
1697
  return `${item.trim()} \\in ${arrayRef}`;
1675
1698
  });
1676
- const _indexMap = new Map;
1677
1699
  result = result.replace(/(\w+(?:\.\w+)*)\[(\d+)\]|\]\[(\d+)\]/g, (_match, identPart, index1, index2) => {
1678
1700
  if (identPart) {
1679
1701
  const newIndex2 = Number.parseInt(index1, 10) + 1;
@@ -1996,7 +2018,8 @@ var init_tla = __esm(() => {
1996
2018
  this.line(" /\\ \\E target \\in msg.targets :");
1997
2019
  this.line(' /\\ IF target \\in Contexts /\\ ports[target] = "connected"');
1998
2020
  this.line(" THEN \\* Successful delivery - route AND invoke handler");
1999
- this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "delivered"]`);
2021
+ this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "delivered",`);
2022
+ this.line(" ![msgIndex].deliveredTo = target]");
2000
2023
  this.line(" /\\ delivered' = delivered \\union {msg.id}");
2001
2024
  this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
2002
2025
  this.line(" pendingRequests[id]]");
@@ -2070,7 +2093,6 @@ var init_tla = __esm(() => {
2070
2093
  }
2071
2094
  this.line("\\* State constraint to bound state space");
2072
2095
  this.addStateConstraint(config, _analysis);
2073
- this.line("=============================================================================");
2074
2096
  }
2075
2097
  addTemporalConstraints(constraints) {
2076
2098
  this.line("\\* Tier 2: Temporal constraint invariants");
@@ -2078,6 +2100,8 @@ var init_tla = __esm(() => {
2078
2100
  this.line("");
2079
2101
  for (let i = 0;i < constraints.length; i++) {
2080
2102
  const constraint = constraints[i];
2103
+ if (!constraint)
2104
+ continue;
2081
2105
  const invName = `TemporalConstraint${i + 1}`;
2082
2106
  if (constraint.description) {
2083
2107
  this.line(`\\* ${constraint.description}`);
@@ -2093,9 +2117,8 @@ var init_tla = __esm(() => {
2093
2117
  this.extractedInvariants.push({
2094
2118
  name: invName,
2095
2119
  description: constraint.description || `${constraint.before} must happen before ${constraint.after}`,
2096
- condition: "",
2097
- confidence: "high",
2098
- source: { file: "", line: 0, column: 0 }
2120
+ expression: `${constraint.before} happens-before ${constraint.after}`,
2121
+ location: { file: "", line: 0 }
2099
2122
  });
2100
2123
  }
2101
2124
  }
@@ -2108,7 +2131,7 @@ var init_tla = __esm(() => {
2108
2131
  this.indent++;
2109
2132
  if (needsConjunction) {
2110
2133
  this.line("/\\ Len(messages) <= MaxMessages");
2111
- if (hasPerMessageBounds) {
2134
+ if (hasPerMessageBounds && config.messages.perMessageBounds) {
2112
2135
  for (const [msgType, _bound] of Object.entries(config.messages.perMessageBounds)) {
2113
2136
  const constName = `MaxMessages_${msgType}`;
2114
2137
  this.line(`/\\ Cardinality({m \\in DOMAIN messages : messages[m].msgType = "${msgType}"}) <= ${constName}`);
@@ -2154,7 +2177,6 @@ var init_tla = __esm(() => {
2154
2177
  this.line(propDef);
2155
2178
  this.line("");
2156
2179
  }
2157
- this.line("=============================================================================");
2158
2180
  }
2159
2181
  fieldConfigToTLAType(_fieldPath, fieldConfig, _config) {
2160
2182
  const typeResult = this.tryAbstractType(fieldConfig) || this.tryBooleanType(fieldConfig) || this.tryEnumType(fieldConfig) || this.tryArrayType(fieldConfig) || this.tryExplicitNumberType(fieldConfig) || this.tryNumberType(fieldConfig) || this.tryStringType(fieldConfig) || this.tryMapType(fieldConfig);
@@ -6501,6 +6523,8 @@ class HandlerExtractor {
6501
6523
  if (match) {
6502
6524
  const signalName = match[1];
6503
6525
  const fieldName = match[2];
6526
+ if (!signalName)
6527
+ return;
6504
6528
  if (fieldName) {
6505
6529
  signals.push(`${signalName}_${fieldName}`);
6506
6530
  } else {
@@ -7693,4 +7717,4 @@ main().catch((error) => {
7693
7717
  process.exit(1);
7694
7718
  });
7695
7719
 
7696
- //# debugId=BB9760A4D35CDBCE64756E2164756E21
7720
+ //# debugId=F1333D1589EFA94964756E2164756E21