@fairfox/polly 0.90.0 → 0.92.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/cli/polly.js +3 -3
- package/dist/cli/polly.js.map +3 -3
- package/dist/gallery/gallery.css +2 -2
- package/dist/gallery/gallery.js +248 -241
- package/dist/src/background/index.js +27 -11
- package/dist/src/background/index.js.map +2 -2
- package/dist/src/background/message-router.js +27 -11
- package/dist/src/background/message-router.js.map +2 -2
- package/dist/src/client/index.js +56 -56
- package/dist/src/client/index.js.map +4 -4
- package/dist/src/elysia/index.js +6 -6
- package/dist/src/elysia/index.js.map +3 -3
- package/dist/src/index.js +51 -23
- package/dist/src/index.js.map +3 -3
- package/dist/src/mesh.js +2 -2
- package/dist/src/mesh.js.map +3 -3
- package/dist/src/peer.js +6 -6
- package/dist/src/peer.js.map +3 -3
- package/dist/src/polly-ui/index.css +2 -2
- package/dist/src/polly-ui/markdown.js +1844 -1010
- package/dist/src/polly-ui/markdown.js.map +5 -5
- package/dist/src/polly-ui/styles.css +2 -2
- package/dist/src/shared/adapters/index.js +27 -11
- package/dist/src/shared/adapters/index.js.map +2 -2
- package/dist/src/shared/lib/context-helpers.js +27 -11
- package/dist/src/shared/lib/context-helpers.js.map +2 -2
- package/dist/src/shared/lib/message-bus.js +27 -11
- package/dist/src/shared/lib/message-bus.js.map +2 -2
- package/dist/src/shared/lib/resource.js +41 -13
- package/dist/src/shared/lib/resource.js.map +3 -3
- package/dist/src/shared/lib/state.js +41 -13
- package/dist/src/shared/lib/state.js.map +3 -3
- package/dist/src/shared/state/app-state.js +41 -13
- package/dist/src/shared/state/app-state.js.map +3 -3
- package/dist/tools/bdd/src/cli.js +20 -2
- package/dist/tools/bdd/src/cli.js.map +4 -4
- package/dist/tools/bdd/src/index.js +20 -2
- package/dist/tools/bdd/src/index.js.map +3 -3
- package/dist/tools/init/templates/pwa/package.json.template +2 -3
- package/dist/tools/mutate/src/args.d.ts +25 -3
- package/dist/tools/mutate/src/cli.js +3234 -312
- package/dist/tools/mutate/src/cli.js.map +32 -12
- package/dist/tools/mutate/src/config.d.ts +13 -2
- package/dist/tools/mutate/src/define.d.ts +76 -0
- package/dist/tools/mutate/src/engine/ast.d.ts +13 -0
- package/dist/tools/mutate/src/engine/baseline.d.ts +20 -0
- package/dist/tools/mutate/src/engine/cache.d.ts +32 -0
- package/dist/tools/mutate/src/engine/coverage.d.ts +60 -0
- package/dist/tools/mutate/src/engine/generate.d.ts +23 -0
- package/dist/tools/mutate/src/engine/graph.d.ts +11 -0
- package/dist/tools/mutate/src/engine/index.d.ts +16 -0
- package/dist/tools/mutate/src/engine/junit.d.ts +38 -0
- package/dist/tools/mutate/src/engine/operators.d.ts +14 -0
- package/dist/tools/mutate/src/engine/preload.d.ts +10 -0
- package/dist/tools/mutate/src/engine/preload.js +180 -0
- package/dist/tools/mutate/src/engine/preload.js.map +10 -0
- package/dist/tools/mutate/src/engine/probes.d.ts +12 -0
- package/dist/tools/mutate/src/engine/report-writer.d.ts +44 -0
- package/dist/tools/mutate/src/engine/runner.d.ts +31 -0
- package/dist/tools/mutate/src/engine/stray-writes.d.ts +25 -0
- package/dist/tools/mutate/src/engine/typecheck.d.ts +37 -0
- package/dist/tools/mutate/src/engine/types.d.ts +191 -0
- package/dist/tools/mutate/src/engine/verify-callsites.d.ts +9 -0
- package/dist/tools/mutate/src/engine/workdir.d.ts +16 -0
- package/dist/tools/mutate/src/engine-config.d.ts +7 -0
- package/dist/tools/mutate/src/index.d.ts +7 -3
- package/dist/tools/mutate/src/index.js +2701 -77
- package/dist/tools/mutate/src/index.js.map +26 -7
- package/dist/tools/mutate/src/ingest.d.ts +30 -1
- package/dist/tools/mutate/src/init.d.ts +2 -1
- package/dist/tools/mutate/src/migrate.d.ts +32 -0
- package/dist/tools/mutate/src/report.d.ts +3 -0
- package/dist/tools/mutate/src/run.d.ts +17 -8
- package/dist/tools/mutate/src/verify-matrix.d.ts +12 -15
- package/dist/tools/quality/src/cli.js +9 -9
- package/dist/tools/quality/src/cli.js.map +4 -4
- package/dist/tools/quality/src/index.js +9 -9
- package/dist/tools/quality/src/index.js.map +4 -4
- package/dist/tools/quality/src/no-tautology-ensures.d.ts +2 -2
- package/dist/tools/quality/src/plugins/import-checks.d.ts +1 -1
- package/dist/tools/test/src/adapters/index.js +3 -2
- package/dist/tools/test/src/adapters/index.js.map +3 -3
- package/dist/tools/test/src/coverage-policy/cli.d.ts +2 -2
- package/dist/tools/test/src/coverage-policy/cli.js +217 -17
- package/dist/tools/test/src/coverage-policy/cli.js.map +7 -5
- package/dist/tools/test/src/coverage-policy/index.d.ts +1 -1
- package/dist/tools/test/src/coverage-policy/index.js +213 -12
- package/dist/tools/test/src/coverage-policy/index.js.map +7 -5
- package/dist/tools/test/src/coverage-policy/mutate-targets.d.ts +29 -12
- package/dist/tools/test/src/e2e-mesh/index.js +6 -6
- package/dist/tools/test/src/e2e-mesh/index.js.map +4 -4
- package/dist/tools/test/src/e2e-relay/index.js +230 -226
- package/dist/tools/test/src/e2e-relay/index.js.map +7 -7
- package/dist/tools/test/src/index.js +3 -2
- package/dist/tools/test/src/index.js.map +3 -3
- package/dist/tools/test/src/visual/index.js +684 -511
- package/dist/tools/test/src/visual/index.js.map +11 -11
- package/dist/tools/verify/src/cli.js +294 -21
- package/dist/tools/verify/src/cli.js.map +9 -9
- package/dist/tools/verify/src/config.d.ts +57 -1
- package/dist/tools/verify/src/config.js +68 -3
- package/dist/tools/verify/src/config.js.map +4 -4
- package/dist/tools/verify/src/primitives/index.d.ts +34 -0
- package/package.json +30 -39
- package/dist/tools/verify/src/stryker/index.d.ts +0 -32
- package/dist/tools/verify/src/stryker/index.js +0 -86
- package/dist/tools/verify/src/stryker/index.js.map +0 -10
|
@@ -246,6 +246,21 @@ var init_expression_validator = __esm(() => {
|
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
// tools/verify/src/codegen/model-constants.ts
|
|
249
|
+
function sanitizeContextName(name) {
|
|
250
|
+
return name.replace(/[^A-Za-z0-9_]/g, "_");
|
|
251
|
+
}
|
|
252
|
+
function hasDeclaredContexts(config) {
|
|
253
|
+
const declared = config?.contexts;
|
|
254
|
+
return Array.isArray(declared) && declared.length > 0;
|
|
255
|
+
}
|
|
256
|
+
function resolveContexts(config) {
|
|
257
|
+
if (!hasDeclaredContexts(config))
|
|
258
|
+
return [...DEFAULT_CONTEXTS];
|
|
259
|
+
return config.contexts.map(sanitizeContextName);
|
|
260
|
+
}
|
|
261
|
+
function targetSetCount(contexts) {
|
|
262
|
+
return 2 ** contexts.length - 1;
|
|
263
|
+
}
|
|
249
264
|
function hasProjectConstant(messages) {
|
|
250
265
|
return messages.maxWorkers !== undefined || messages.maxRenderers !== undefined || messages.maxContexts !== undefined || messages.maxClients !== undefined;
|
|
251
266
|
}
|
|
@@ -262,14 +277,13 @@ function generatedTabValues(messages) {
|
|
|
262
277
|
function generatedTabCount(messages) {
|
|
263
278
|
return generatedTabValues(messages).length;
|
|
264
279
|
}
|
|
265
|
-
function sendBranchingFactor(handlerCount, tabCount) {
|
|
266
|
-
return
|
|
280
|
+
function sendBranchingFactor(handlerCount, tabCount, contexts) {
|
|
281
|
+
return contexts.length * targetSetCount(contexts) * tabCount * handlerCount;
|
|
267
282
|
}
|
|
268
|
-
var
|
|
283
|
+
var DEFAULT_CONTEXTS, CONTEXT_NAME_PATTERN;
|
|
269
284
|
var init_model_constants = __esm(() => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
TARGET_SET_COUNT = 2 ** CONTEXT_COUNT - 1;
|
|
285
|
+
DEFAULT_CONTEXTS = ["background", "content", "popup"];
|
|
286
|
+
CONTEXT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
273
287
|
});
|
|
274
288
|
|
|
275
289
|
// tools/verify/src/runner/memory.ts
|
|
@@ -1041,7 +1055,7 @@ var init_tla = __esm(() => {
|
|
|
1041
1055
|
lines.push("CONSTANTS");
|
|
1042
1056
|
}
|
|
1043
1057
|
addBasicConstants(lines, config) {
|
|
1044
|
-
lines.push(` Contexts = {${
|
|
1058
|
+
lines.push(` Contexts = {${resolveContexts(config).join(", ")}}`);
|
|
1045
1059
|
lines.push(` MaxMessages = ${config.messages.maxInFlight || 3}`);
|
|
1046
1060
|
lines.push(" NULL = NULL");
|
|
1047
1061
|
if (config.messages.perMessageBounds) {
|
|
@@ -1062,7 +1076,6 @@ var init_tla = __esm(() => {
|
|
|
1062
1076
|
hasProjectConstant = true;
|
|
1063
1077
|
}
|
|
1064
1078
|
if ("maxContexts" in messages && messages.maxContexts !== undefined) {
|
|
1065
|
-
lines.push(` MaxContexts = ${messages.maxContexts}`);
|
|
1066
1079
|
hasProjectConstant = true;
|
|
1067
1080
|
}
|
|
1068
1081
|
if ("maxClients" in messages && messages.maxClients !== undefined && !hasProjectConstant) {
|
|
@@ -3965,6 +3978,9 @@ function getTabSetInput(config) {
|
|
|
3965
3978
|
}
|
|
3966
3979
|
return {};
|
|
3967
3980
|
}
|
|
3981
|
+
function getContextSetInput(config) {
|
|
3982
|
+
return config;
|
|
3983
|
+
}
|
|
3968
3984
|
function getFeasibility(states) {
|
|
3969
3985
|
if (states < 1e5)
|
|
3970
3986
|
return "trivial";
|
|
@@ -3989,7 +4005,7 @@ function feasibilityLabel(f) {
|
|
|
3989
4005
|
var OMITTED_VARIABLES = "ports, status, deliveredTo, time and payload";
|
|
3990
4006
|
function dominantTerm(e) {
|
|
3991
4007
|
const log10 = (n) => n > 0 ? Math.log10(n) : 0;
|
|
3992
|
-
const topology = e.contextCount *
|
|
4008
|
+
const topology = e.contextCount * e.targetSets * e.tabCount;
|
|
3993
4009
|
const terms = [
|
|
3994
4010
|
{
|
|
3995
4011
|
label: `state fields (${e.fieldProduct} combinations across ${e.contextCount} contexts)`,
|
|
@@ -4000,7 +4016,7 @@ function dominantTerm(e) {
|
|
|
4000
4016
|
decades: e.maxInFlight * log10(e.handlerCount)
|
|
4001
4017
|
},
|
|
4002
4018
|
{
|
|
4003
|
-
label: `send topology (${e.contextCount} sources x ${
|
|
4019
|
+
label: `send topology (${e.contextCount} sources x ${e.targetSets} target sets x ${e.tabCount} tabs)`,
|
|
4004
4020
|
decades: e.maxInFlight * log10(topology)
|
|
4005
4021
|
}
|
|
4006
4022
|
];
|
|
@@ -4027,9 +4043,12 @@ function estimateForScope(config, scope) {
|
|
|
4027
4043
|
const { handlerCount, maxInFlight } = scope;
|
|
4028
4044
|
const tabSetInput = getTabSetInput(config);
|
|
4029
4045
|
const tabCount = generatedTabCount(tabSetInput);
|
|
4030
|
-
const
|
|
4046
|
+
const contextSetInput = getContextSetInput(config);
|
|
4047
|
+
const contexts = resolveContexts(contextSetInput);
|
|
4048
|
+
const contextsDeclared = hasDeclaredContexts(contextSetInput);
|
|
4049
|
+
const contextCount = contexts.length;
|
|
4031
4050
|
const totalStateSpace = fieldProduct ** contextCount;
|
|
4032
|
-
const sendBranching = sendBranchingFactor(handlerCount, tabCount);
|
|
4051
|
+
const sendBranching = sendBranchingFactor(handlerCount, tabCount, contexts);
|
|
4033
4052
|
const interleavingFactor = sendBranching ** maxInFlight;
|
|
4034
4053
|
const estimatedStates = totalStateSpace * interleavingFactor;
|
|
4035
4054
|
const feasibility = getFeasibility(estimatedStates);
|
|
@@ -4041,6 +4060,7 @@ function estimateForScope(config, scope) {
|
|
|
4041
4060
|
fieldProduct,
|
|
4042
4061
|
handlerCount,
|
|
4043
4062
|
contextCount,
|
|
4063
|
+
targetSets: targetSetCount(contexts),
|
|
4044
4064
|
tabCount,
|
|
4045
4065
|
maxInFlight
|
|
4046
4066
|
});
|
|
@@ -4048,6 +4068,9 @@ function estimateForScope(config, scope) {
|
|
|
4048
4068
|
if (maxInFlight > 1) {
|
|
4049
4069
|
suggestions.push(`maxInFlight ${maxInFlight} → ${maxInFlight - 1} divides the estimate by ${sendBranching.toLocaleString()}x (one fewer send to branch over)`);
|
|
4050
4070
|
}
|
|
4071
|
+
if (!contextsDeclared) {
|
|
4072
|
+
suggestions.push(`Contexts is the default {${contexts.join(", ")}} — no \`contexts\` key is declared. ` + `Declaring the ones this project has divides state replication by fieldProduct^(${contextCount} - n) ` + "and send branching by the drop in |Contexts| * (2^|Contexts| - 1) (polly#185)");
|
|
4073
|
+
}
|
|
4051
4074
|
if (handlerCount > 15) {
|
|
4052
4075
|
suggestions.push(`${handlerCount} handlers enter the estimate as ${handlerCount}^${maxInFlight} — splitting into subsystems is the cheapest cut`);
|
|
4053
4076
|
}
|
|
@@ -4063,6 +4086,8 @@ function estimateForScope(config, scope) {
|
|
|
4063
4086
|
handlerCount,
|
|
4064
4087
|
maxInFlight,
|
|
4065
4088
|
contextCount,
|
|
4089
|
+
contexts,
|
|
4090
|
+
contextsDeclared,
|
|
4066
4091
|
tabCount,
|
|
4067
4092
|
sendBranching,
|
|
4068
4093
|
totalStateSpace,
|
|
@@ -4104,6 +4129,7 @@ class ConfigGenerator {
|
|
|
4104
4129
|
this.addHeader();
|
|
4105
4130
|
this.addImports();
|
|
4106
4131
|
this.addExport();
|
|
4132
|
+
this.addContextsConfig(analysis);
|
|
4107
4133
|
this.addStateConfig(analysis.fields, analysis.resources);
|
|
4108
4134
|
this.addMessagesConfig();
|
|
4109
4135
|
this.addBehaviorConfig();
|
|
@@ -4339,6 +4365,53 @@ class ConfigGenerator {
|
|
|
4339
4365
|
}
|
|
4340
4366
|
return "{ min: /* CONFIGURE */ null, max: /* CONFIGURE */ null }";
|
|
4341
4367
|
}
|
|
4368
|
+
addContextsConfig(analysis) {
|
|
4369
|
+
const inferred = [];
|
|
4370
|
+
for (const handler of analysis.handlers ?? []) {
|
|
4371
|
+
const node = handler.node;
|
|
4372
|
+
if (node === undefined || node === "unknown")
|
|
4373
|
+
continue;
|
|
4374
|
+
if (!inferred.includes(node))
|
|
4375
|
+
inferred.push(node);
|
|
4376
|
+
}
|
|
4377
|
+
this.line("// ─────────────────────────────────────────────────────────");
|
|
4378
|
+
this.line("// Contexts — the processes/actors messages are routed between");
|
|
4379
|
+
this.line("// ─────────────────────────────────────────────────────────");
|
|
4380
|
+
this.line("// This is the cheapest term in the state space to cut. The model");
|
|
4381
|
+
this.line("// replicates state across the set and quantifies every send over");
|
|
4382
|
+
this.line("// |Contexts| x (2^|Contexts| - 1) source/target pairs.");
|
|
4383
|
+
this.line("//");
|
|
4384
|
+
this.line("// Measured on a two-handler model, one message in flight, one tab:");
|
|
4385
|
+
this.line("// • 3 contexts: 38,464 distinct states");
|
|
4386
|
+
this.line("// • 2 contexts: 4,896 distinct states");
|
|
4387
|
+
this.line("// • 1 context: 368 distinct states");
|
|
4388
|
+
this.line("//");
|
|
4389
|
+
const source = inferred.length > 0 ? inferred : this.projectTypeContexts();
|
|
4390
|
+
if (source.length > 0) {
|
|
4391
|
+
this.line(inferred.length > 0 ? "// ✓ Auto-configured from the file paths of the handlers" : `// ✓ Auto-configured from the project type (${this.projectType})`);
|
|
4392
|
+
this.line(`contexts: /* REVIEW */ [${source.map((c) => `'${c}'`).join(", ")}],`);
|
|
4393
|
+
} else {
|
|
4394
|
+
this.line("// No context could be inferred from the handler file paths.");
|
|
4395
|
+
this.line("// Omitting the key models the browser-extension default:");
|
|
4396
|
+
this.line("// ['background', 'content', 'popup']");
|
|
4397
|
+
this.line("// contexts: /* CONFIGURE */ ['server'],");
|
|
4398
|
+
}
|
|
4399
|
+
this.line("");
|
|
4400
|
+
}
|
|
4401
|
+
projectTypeContexts() {
|
|
4402
|
+
switch (this.projectType) {
|
|
4403
|
+
case "websocket-app":
|
|
4404
|
+
return ["server", "client"];
|
|
4405
|
+
case "pwa":
|
|
4406
|
+
return ["ServiceWorker", "Window"];
|
|
4407
|
+
case "electron":
|
|
4408
|
+
return ["main", "renderer"];
|
|
4409
|
+
case "chrome-extension":
|
|
4410
|
+
return ["background", "content", "popup"];
|
|
4411
|
+
default:
|
|
4412
|
+
return [];
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4342
4415
|
addMessagesConfig() {
|
|
4343
4416
|
this.line("messages: {");
|
|
4344
4417
|
this.indent++;
|
|
@@ -4408,13 +4481,6 @@ class ConfigGenerator {
|
|
|
4408
4481
|
this.line("maxTabs: 1,");
|
|
4409
4482
|
break;
|
|
4410
4483
|
case "generic":
|
|
4411
|
-
this.line("// Maximum contexts/actors to model.");
|
|
4412
|
-
this.line("//");
|
|
4413
|
-
this.line("// Recommended:");
|
|
4414
|
-
this.line("// • 2-3: Simple message passing");
|
|
4415
|
-
this.line("// • 4-5: Complex coordination");
|
|
4416
|
-
this.line("//");
|
|
4417
|
-
this.line("maxContexts: 3,");
|
|
4418
4484
|
break;
|
|
4419
4485
|
}
|
|
4420
4486
|
this.line("");
|
|
@@ -4546,7 +4612,11 @@ function generateConfig(analysis, projectType = "chrome-extension") {
|
|
|
4546
4612
|
return generator.generate(analysis, projectType);
|
|
4547
4613
|
}
|
|
4548
4614
|
|
|
4615
|
+
// tools/verify/src/cli.ts
|
|
4616
|
+
init_model_constants();
|
|
4617
|
+
|
|
4549
4618
|
// tools/verify/src/config/parser.ts
|
|
4619
|
+
init_model_constants();
|
|
4550
4620
|
init_memory();
|
|
4551
4621
|
import * as fs from "node:fs";
|
|
4552
4622
|
import * as path from "node:path";
|
|
@@ -4629,6 +4699,45 @@ function validateCoupledFields(coupledFields, stateConfig) {
|
|
|
4629
4699
|
}
|
|
4630
4700
|
|
|
4631
4701
|
// tools/verify/src/config/parser.ts
|
|
4702
|
+
var RESERVED_CONTEXT_NAMES = new Set([
|
|
4703
|
+
"Contexts",
|
|
4704
|
+
"MaxMessages",
|
|
4705
|
+
"NULL",
|
|
4706
|
+
"Tabs",
|
|
4707
|
+
"TimeoutLimit",
|
|
4708
|
+
"MaxWorkers",
|
|
4709
|
+
"MaxRenderers",
|
|
4710
|
+
"MaxClients",
|
|
4711
|
+
"NoTarget",
|
|
4712
|
+
"MeshDocs",
|
|
4713
|
+
"ASSUME",
|
|
4714
|
+
"BOOLEAN",
|
|
4715
|
+
"CASE",
|
|
4716
|
+
"CHOOSE",
|
|
4717
|
+
"CONSTANT",
|
|
4718
|
+
"CONSTANTS",
|
|
4719
|
+
"DOMAIN",
|
|
4720
|
+
"ELSE",
|
|
4721
|
+
"ENABLED",
|
|
4722
|
+
"EXCEPT",
|
|
4723
|
+
"EXTENDS",
|
|
4724
|
+
"FALSE",
|
|
4725
|
+
"IF",
|
|
4726
|
+
"IN",
|
|
4727
|
+
"INSTANCE",
|
|
4728
|
+
"LET",
|
|
4729
|
+
"MODULE",
|
|
4730
|
+
"OTHER",
|
|
4731
|
+
"SUBSET",
|
|
4732
|
+
"THEN",
|
|
4733
|
+
"TRUE",
|
|
4734
|
+
"UNCHANGED",
|
|
4735
|
+
"UNION",
|
|
4736
|
+
"VARIABLE",
|
|
4737
|
+
"VARIABLES",
|
|
4738
|
+
"WITH"
|
|
4739
|
+
]);
|
|
4740
|
+
|
|
4632
4741
|
class ConfigValidator {
|
|
4633
4742
|
issues = [];
|
|
4634
4743
|
validate(configPath) {
|
|
@@ -4742,6 +4851,7 @@ class ConfigValidator {
|
|
|
4742
4851
|
this.findNullPlaceholders(config.messages, "messages");
|
|
4743
4852
|
this.validateBounds(config);
|
|
4744
4853
|
this.validateTier1Optimizations(config.messages);
|
|
4854
|
+
this.validateContexts(config);
|
|
4745
4855
|
if (config.verification) {
|
|
4746
4856
|
this.validateVerificationOptions(config.verification);
|
|
4747
4857
|
}
|
|
@@ -5023,6 +5133,115 @@ class ConfigValidator {
|
|
|
5023
5133
|
}
|
|
5024
5134
|
this.validateMemory(verification.memory);
|
|
5025
5135
|
}
|
|
5136
|
+
validateContexts(config) {
|
|
5137
|
+
this.warnOnDeadMaxContexts(config);
|
|
5138
|
+
const contexts = config.contexts;
|
|
5139
|
+
if (contexts === undefined)
|
|
5140
|
+
return;
|
|
5141
|
+
if (!Array.isArray(contexts)) {
|
|
5142
|
+
this.issues.push({
|
|
5143
|
+
type: "invalid_value",
|
|
5144
|
+
severity: "error",
|
|
5145
|
+
field: "contexts",
|
|
5146
|
+
message: "contexts must be an array of context names",
|
|
5147
|
+
suggestion: `Use an array, e.g. ["server"], or omit the key for the default {${DEFAULT_CONTEXTS.join(", ")}}`
|
|
5148
|
+
});
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5151
|
+
if (contexts.length === 0) {
|
|
5152
|
+
this.issues.push({
|
|
5153
|
+
type: "invalid_value",
|
|
5154
|
+
severity: "error",
|
|
5155
|
+
field: "contexts",
|
|
5156
|
+
message: "contexts is empty; the model needs at least one context",
|
|
5157
|
+
suggestion: `Name at least one context, e.g. ["server"], or omit the key for the default {${DEFAULT_CONTEXTS.join(", ")}}`
|
|
5158
|
+
});
|
|
5159
|
+
return;
|
|
5160
|
+
}
|
|
5161
|
+
const seen = new Map;
|
|
5162
|
+
for (const name of contexts) {
|
|
5163
|
+
this.validateContextName(name, seen);
|
|
5164
|
+
}
|
|
5165
|
+
this.warnOnSingleContextMesh(config, contexts.length);
|
|
5166
|
+
}
|
|
5167
|
+
validateContextName(name, seen) {
|
|
5168
|
+
if (typeof name !== "string") {
|
|
5169
|
+
this.issues.push({
|
|
5170
|
+
type: "invalid_value",
|
|
5171
|
+
severity: "error",
|
|
5172
|
+
field: "contexts",
|
|
5173
|
+
message: `Context name ${JSON.stringify(name)} is not a string`,
|
|
5174
|
+
suggestion: "Every entry must be a string naming one context"
|
|
5175
|
+
});
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
const emitted = sanitizeContextName(name);
|
|
5179
|
+
if (!CONTEXT_NAME_PATTERN.test(emitted)) {
|
|
5180
|
+
this.issues.push({
|
|
5181
|
+
type: "invalid_value",
|
|
5182
|
+
severity: "error",
|
|
5183
|
+
field: "contexts",
|
|
5184
|
+
message: `Context "${name}" is not a TLA+ model value (emits as "${emitted}")`,
|
|
5185
|
+
suggestion: "Start with a letter or underscore, then letters, digits or underscores"
|
|
5186
|
+
});
|
|
5187
|
+
return;
|
|
5188
|
+
}
|
|
5189
|
+
if (RESERVED_CONTEXT_NAMES.has(emitted)) {
|
|
5190
|
+
this.issues.push({
|
|
5191
|
+
type: "invalid_value",
|
|
5192
|
+
severity: "error",
|
|
5193
|
+
field: "contexts",
|
|
5194
|
+
message: `Context "${name}" collides with a TLA+ keyword or a constant the .cfg already declares`,
|
|
5195
|
+
suggestion: `Pick a name outside {${[...RESERVED_CONTEXT_NAMES].sort().join(", ")}}`
|
|
5196
|
+
});
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5199
|
+
const collidesWith = seen.get(emitted);
|
|
5200
|
+
if (collidesWith !== undefined) {
|
|
5201
|
+
this.issues.push({
|
|
5202
|
+
type: "invalid_value",
|
|
5203
|
+
severity: "error",
|
|
5204
|
+
field: "contexts",
|
|
5205
|
+
message: collidesWith === name ? `Context "${name}" is declared twice` : `Contexts "${collidesWith}" and "${name}" both emit as "${emitted}"`,
|
|
5206
|
+
suggestion: "Each context must emit a distinct TLA+ model value"
|
|
5207
|
+
});
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
seen.set(emitted, name);
|
|
5211
|
+
if (emitted !== name) {
|
|
5212
|
+
this.issues.push({
|
|
5213
|
+
type: "invalid_value",
|
|
5214
|
+
severity: "warning",
|
|
5215
|
+
field: "contexts",
|
|
5216
|
+
message: `Context "${name}" is emitted as the model value "${emitted}"`,
|
|
5217
|
+
suggestion: "Name it that way in the config so the spec and the config read alike"
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
warnOnDeadMaxContexts(config) {
|
|
5222
|
+
if (config.messages?.maxContexts === undefined)
|
|
5223
|
+
return;
|
|
5224
|
+
this.issues.push({
|
|
5225
|
+
type: "invalid_value",
|
|
5226
|
+
severity: "warning",
|
|
5227
|
+
field: "messages.maxContexts",
|
|
5228
|
+
message: "messages.maxContexts does not size the Contexts set and never did",
|
|
5229
|
+
suggestion: 'Declare the set instead, e.g. contexts: ["server", "client"]'
|
|
5230
|
+
});
|
|
5231
|
+
}
|
|
5232
|
+
warnOnSingleContextMesh(config, contextCount) {
|
|
5233
|
+
if (contextCount > 1)
|
|
5234
|
+
return;
|
|
5235
|
+
if (!config.mesh || Object.keys(config.mesh).length === 0)
|
|
5236
|
+
return;
|
|
5237
|
+
this.issues.push({
|
|
5238
|
+
type: "invalid_value",
|
|
5239
|
+
severity: "warning",
|
|
5240
|
+
field: "contexts",
|
|
5241
|
+
message: "one context with a `mesh` block: PropagateMeshOp needs two distinct contexts, so mesh propagation is never enabled",
|
|
5242
|
+
suggestion: "Declare at least two contexts, or drop the mesh block from this model"
|
|
5243
|
+
});
|
|
5244
|
+
}
|
|
5026
5245
|
validateMemory(memory) {
|
|
5027
5246
|
if (memory === undefined)
|
|
5028
5247
|
return;
|
|
@@ -8822,7 +9041,8 @@ function displayEstimate(estimate) {
|
|
|
8822
9041
|
console.log(` Field combinations: ${color(String(estimate.fieldProduct), COLORS.green)}`);
|
|
8823
9042
|
console.log(` Handlers: ${estimate.handlerCount}`);
|
|
8824
9043
|
console.log(` Max in-flight: ${estimate.maxInFlight}`);
|
|
8825
|
-
|
|
9044
|
+
const contextSource = estimate.contextsDeclared ? "declared" : "the default — no `contexts` key";
|
|
9045
|
+
console.log(` Contexts: ${estimate.contextCount} — ${estimate.contexts.join(", ")} (${contextSource})`);
|
|
8826
9046
|
console.log(` Tabs: ${estimate.tabCount}`);
|
|
8827
9047
|
console.log(` State across contexts: ${estimate.fieldProduct}^${estimate.contextCount} = ${estimate.totalStateSpace.toLocaleString()}`);
|
|
8828
9048
|
console.log(` Send branching: ${estimate.sendBranching.toLocaleString()} per message (^${estimate.maxInFlight} = ${estimate.interleavingFactor.toLocaleString()})`);
|
|
@@ -9086,6 +9306,55 @@ async function runCoupledFieldsLint(config, analysis) {
|
|
|
9086
9306
|
console.log(color(" the relationship with a `capabilities` invariant.", COLORS.yellow));
|
|
9087
9307
|
console.log();
|
|
9088
9308
|
}
|
|
9309
|
+
function describeContextSet(config) {
|
|
9310
|
+
const contexts = resolveContexts(config);
|
|
9311
|
+
const source = hasDeclaredContexts(config) ? "declared" : "default";
|
|
9312
|
+
return `${contexts.join(", ")} (${contexts.length}, ${source})`;
|
|
9313
|
+
}
|
|
9314
|
+
function inferredContexts(analysis) {
|
|
9315
|
+
const seen = [];
|
|
9316
|
+
for (const handler of analysis.handlers) {
|
|
9317
|
+
const node = handler.node;
|
|
9318
|
+
if (node === undefined || node === "unknown")
|
|
9319
|
+
continue;
|
|
9320
|
+
if (!seen.includes(node))
|
|
9321
|
+
seen.push(node);
|
|
9322
|
+
}
|
|
9323
|
+
return seen;
|
|
9324
|
+
}
|
|
9325
|
+
function displayContextDiagnostics(config, analysis) {
|
|
9326
|
+
const declared = hasDeclaredContexts(config);
|
|
9327
|
+
const contexts = new Set(resolveContexts(config));
|
|
9328
|
+
const inferred = inferredContexts(analysis);
|
|
9329
|
+
if (!declared) {
|
|
9330
|
+
console.log(color(`ℹ️ No \`contexts\` key — modelling the default {${DEFAULT_CONTEXTS.join(", ")}}`, COLORS.gray));
|
|
9331
|
+
if (inferred.length > 0) {
|
|
9332
|
+
console.log(color(` polly infers from the handler paths: ${inferred.join(", ")}`, COLORS.gray));
|
|
9333
|
+
console.log(color(` Declaring \`contexts: [${inferred.map((c) => `"${c}"`).join(", ")}]\` shrinks the model to the contexts you have.`, COLORS.gray));
|
|
9334
|
+
}
|
|
9335
|
+
console.log();
|
|
9336
|
+
return;
|
|
9337
|
+
}
|
|
9338
|
+
const outside = analysis.handlers.filter((h) => {
|
|
9339
|
+
const node = h.node;
|
|
9340
|
+
return node !== undefined && node !== "unknown" && !contexts.has(sanitizeContextName(node));
|
|
9341
|
+
});
|
|
9342
|
+
if (outside.length === 0)
|
|
9343
|
+
return;
|
|
9344
|
+
console.log(color(`⚠️ ${outside.length} handler(s) run in a context \`contexts\` does not declare:`, COLORS.yellow));
|
|
9345
|
+
for (const h of outside.slice(0, 10)) {
|
|
9346
|
+
const node = h.node;
|
|
9347
|
+
console.log(color(` • ${h.messageType} → ${node} (${h.location.file})`, COLORS.yellow));
|
|
9348
|
+
}
|
|
9349
|
+
if (outside.length > 10) {
|
|
9350
|
+
console.log(color(` ... and ${outside.length - 10} more`, COLORS.yellow));
|
|
9351
|
+
}
|
|
9352
|
+
console.log();
|
|
9353
|
+
console.log(color(` The model quantifies sends over {${[...contexts].join(", ")}} only, so these handlers`, COLORS.yellow));
|
|
9354
|
+
console.log(color(" are modelled from a source they are not in. Add the missing contexts, or correct the", COLORS.yellow));
|
|
9355
|
+
console.log(color(" file paths polly infers them from.", COLORS.yellow));
|
|
9356
|
+
console.log();
|
|
9357
|
+
}
|
|
9089
9358
|
async function runFullVerification(configPath) {
|
|
9090
9359
|
const config = await loadVerificationConfig(configPath);
|
|
9091
9360
|
const analysis = await runCodebaseAnalysis();
|
|
@@ -9100,6 +9369,7 @@ async function runFullVerification(configPath) {
|
|
|
9100
9369
|
const meshFindingCount = displayMeshOrPeerSignalWarnings(typedAnalysis, declaredMeshDocs);
|
|
9101
9370
|
await runCoupledFieldsLint(typedConfig, typedAnalysis);
|
|
9102
9371
|
await runModelCoverage(typedConfig, typedAnalysis, meshFindingCount);
|
|
9372
|
+
displayContextDiagnostics(typedConfig, typedAnalysis);
|
|
9103
9373
|
if (typedConfig.subsystems && Object.keys(typedConfig.subsystems).length > 0) {
|
|
9104
9374
|
await runSubsystemVerification(typedConfig, typedAnalysis);
|
|
9105
9375
|
if (isWitnessMode()) {
|
|
@@ -9138,6 +9408,7 @@ async function runMonolithicVerification(config, analysis) {
|
|
|
9138
9408
|
console.log(color(` Max depth: ${maxDepth}`, COLORS.gray));
|
|
9139
9409
|
}
|
|
9140
9410
|
console.log(color(` Memory: ${memory ?? DEFAULT_TLC_MEMORY}`, COLORS.gray));
|
|
9411
|
+
console.log(color(` Contexts: ${describeContextSet(typedConfig)}`, COLORS.gray));
|
|
9141
9412
|
console.log();
|
|
9142
9413
|
const result = await docker.runTLC(specPath, {
|
|
9143
9414
|
workers,
|
|
@@ -9213,6 +9484,8 @@ async function runSubsystemVerification(config, analysis) {
|
|
|
9213
9484
|
const workers = getWorkers(config);
|
|
9214
9485
|
const maxDepth = getMaxDepth(config);
|
|
9215
9486
|
const memory = getMemory(config);
|
|
9487
|
+
console.log(color(`⚙️ Contexts: ${describeContextSet(config)}`, COLORS.gray));
|
|
9488
|
+
console.log();
|
|
9216
9489
|
await Promise.resolve().then(() => init_tla());
|
|
9217
9490
|
const results = [];
|
|
9218
9491
|
const customTLAPaths = getCustomTLAPaths(config);
|
|
@@ -9832,4 +10105,4 @@ main().catch((error) => {
|
|
|
9832
10105
|
process.exit(1);
|
|
9833
10106
|
});
|
|
9834
10107
|
|
|
9835
|
-
//# debugId=
|
|
10108
|
+
//# debugId=471A448DCAABB9CF64756E2164756E21
|