@fairfox/polly 0.89.1 → 0.90.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.
@@ -245,6 +245,66 @@ var init_expression_validator = __esm(() => {
245
245
  WEAK_NEGATION = /([a-zA-Z_][\w.]*(?:\.value\.[\w.]+|\.value\b|))?\s*!==?\s*("[^"]*"|'[^']*'|\d+|true|false|null)/;
246
246
  });
247
247
 
248
+ // tools/verify/src/codegen/model-constants.ts
249
+ function hasProjectConstant(messages) {
250
+ return messages.maxWorkers !== undefined || messages.maxRenderers !== undefined || messages.maxContexts !== undefined || messages.maxClients !== undefined;
251
+ }
252
+ function generatedTabValues(messages) {
253
+ if (messages.tabSymmetry) {
254
+ const maxTabs = messages.maxTabs ?? 1;
255
+ return Array.from({ length: maxTabs + 1 }, (_, i) => `Tab${i}`);
256
+ }
257
+ if (messages.maxTabs !== undefined && messages.maxTabs !== null) {
258
+ return Array.from({ length: messages.maxTabs + 1 }, (_, i) => String(i));
259
+ }
260
+ return hasProjectConstant(messages) ? ["0"] : ["0", "1"];
261
+ }
262
+ function generatedTabCount(messages) {
263
+ return generatedTabValues(messages).length;
264
+ }
265
+ function sendBranchingFactor(handlerCount, tabCount) {
266
+ return CONTEXT_COUNT * TARGET_SET_COUNT * tabCount * handlerCount;
267
+ }
268
+ var GENERATED_CONTEXTS, CONTEXT_COUNT, TARGET_SET_COUNT;
269
+ var init_model_constants = __esm(() => {
270
+ GENERATED_CONTEXTS = ["background", "content", "popup"];
271
+ CONTEXT_COUNT = GENERATED_CONTEXTS.length;
272
+ TARGET_SET_COUNT = 2 ** CONTEXT_COUNT - 1;
273
+ });
274
+
275
+ // tools/verify/src/runner/memory.ts
276
+ function parseMemoryBytes(spec) {
277
+ const match = /^(\d+)\s*([bkmg])?$/i.exec(spec.trim());
278
+ if (!match?.[1])
279
+ return null;
280
+ const value = Number.parseInt(match[1], 10);
281
+ if (value <= 0)
282
+ return null;
283
+ const unit = (match[2] ?? "b").toLowerCase();
284
+ const multiplier = UNIT_BYTES[unit];
285
+ if (multiplier === undefined)
286
+ return null;
287
+ return value * multiplier;
288
+ }
289
+ function jvmHeapArg(memoryBytes) {
290
+ const megabytes = Math.max(64, Math.floor(memoryBytes * JVM_HEAP_FRACTION / MEGABYTE));
291
+ return `${megabytes}m`;
292
+ }
293
+ function javaToolOptions(memoryBytes) {
294
+ return `-Xmx${jvmHeapArg(memoryBytes)} -XX:+UseParallelGC`;
295
+ }
296
+ var DEFAULT_TLC_MEMORY = "4g", MIN_TLC_MEMORY = "512m", JVM_HEAP_FRACTION = 0.75, MEGABYTE, UNIT_BYTES, MIN_TLC_MEMORY_BYTES;
297
+ var init_memory = __esm(() => {
298
+ MEGABYTE = 1024 ** 2;
299
+ UNIT_BYTES = {
300
+ b: 1,
301
+ k: 1024,
302
+ m: MEGABYTE,
303
+ g: 1024 ** 3
304
+ };
305
+ MIN_TLC_MEMORY_BYTES = parseMemoryBytes(MIN_TLC_MEMORY) ?? 512 * 1024 ** 2;
306
+ });
307
+
248
308
  // tools/verify/src/analysis/model-coverage.ts
249
309
  function norm(field) {
250
310
  return field.replace(/_/g, ".");
@@ -751,6 +811,7 @@ async function generateSubsystemTLA(_subsystemName, subsystem, config, analysis)
751
811
  }
752
812
  var TLAValidationError, TLAGenerator;
753
813
  var init_tla = __esm(() => {
814
+ init_model_constants();
754
815
  init_temporal();
755
816
  TLAValidationError = class TLAValidationError extends Error {
756
817
  report;
@@ -770,7 +831,6 @@ var init_tla = __esm(() => {
770
831
  symmetrySets = [];
771
832
  resolvedActionNames = new Map;
772
833
  paramDomains = new Map;
773
- tabSymmetryEnabled = false;
774
834
  tabCount = 0;
775
835
  moduleName = "UserApp";
776
836
  currentConfig;
@@ -981,7 +1041,7 @@ var init_tla = __esm(() => {
981
1041
  lines.push("CONSTANTS");
982
1042
  }
983
1043
  addBasicConstants(lines, config) {
984
- lines.push(" Contexts = {background, content, popup}");
1044
+ lines.push(` Contexts = {${GENERATED_CONTEXTS.join(", ")}}`);
985
1045
  lines.push(` MaxMessages = ${config.messages.maxInFlight || 3}`);
986
1046
  lines.push(" NULL = NULL");
987
1047
  if (config.messages.perMessageBounds) {
@@ -1007,22 +1067,14 @@ var init_tla = __esm(() => {
1007
1067
  }
1008
1068
  if ("maxClients" in messages && messages.maxClients !== undefined && !hasProjectConstant) {
1009
1069
  lines.push(` MaxClients = ${messages.maxClients}`);
1010
- hasProjectConstant = true;
1011
1070
  }
1012
- if (this.tabSymmetryEnabled) {
1013
- for (let i = 0;i < this.tabCount; i++) {
1014
- lines.push(` Tab${i} = Tab${i}`);
1015
- }
1016
- const tabValues = Array.from({ length: this.tabCount }, (_, i) => `Tab${i}`).join(", ");
1017
- lines.push(` Tabs = {${tabValues}}`);
1018
- } else if ("maxTabs" in messages && messages.maxTabs !== undefined && messages.maxTabs !== null) {
1019
- const tabValues = Array.from({ length: messages.maxTabs + 1 }, (_, i) => i).join(", ");
1020
- lines.push(` Tabs = {${tabValues}}`);
1021
- } else if (hasProjectConstant) {
1022
- lines.push(" Tabs = {0}");
1023
- } else {
1024
- lines.push(" Tabs = {0, 1}");
1071
+ const tabValues = generatedTabValues(messages);
1072
+ if (messages.tabSymmetry) {
1073
+ for (const tab of tabValues) {
1074
+ lines.push(` ${tab} = ${tab}`);
1075
+ }
1025
1076
  }
1077
+ lines.push(` Tabs = {${tabValues.join(", ")}}`);
1026
1078
  lines.push(" TimeoutLimit = 3");
1027
1079
  }
1028
1080
  addStateBoundsConstants(lines, state) {
@@ -1442,7 +1494,6 @@ var init_tla = __esm(() => {
1442
1494
  }
1443
1495
  const maxTabs = config.messages.maxTabs ?? 1;
1444
1496
  this.tabCount = maxTabs + 1;
1445
- this.tabSymmetryEnabled = true;
1446
1497
  this.line("\\* Tab symmetry constants for state space reduction (Tier 1 optimization)");
1447
1498
  this.line("CONSTANTS");
1448
1499
  this.indent++;
@@ -3562,11 +3613,20 @@ class DockerRunner {
3562
3613
  }
3563
3614
  const containerName = mintContainerName("tlc");
3564
3615
  sweepOrphanedContainersOnce();
3616
+ const memory = options?.memory ?? DEFAULT_TLC_MEMORY;
3617
+ const memoryBytes = parseMemoryBytes(memory);
3618
+ if (memoryBytes === null) {
3619
+ throw new Error(`Invalid verification.memory: "${memory}". Use a docker-style size such as "512m" or "8g".`);
3620
+ }
3565
3621
  const args = [
3566
3622
  "run",
3567
3623
  "--rm",
3568
3624
  "--name",
3569
3625
  containerName,
3626
+ "--memory",
3627
+ memory,
3628
+ "-e",
3629
+ `JAVA_TOOL_OPTIONS=${javaToolOptions(memoryBytes)}`,
3570
3630
  "-v",
3571
3631
  `${specDir}:/work`,
3572
3632
  this.IMAGE_NAME,
@@ -3585,9 +3645,9 @@ class DockerRunner {
3585
3645
  timeout: options?.timeout,
3586
3646
  containerName
3587
3647
  });
3588
- return this.parseTLCOutput(result);
3648
+ return this.parseTLCOutput(result, memory);
3589
3649
  }
3590
- parseTLCOutput(result) {
3650
+ parseTLCOutput(result, memory) {
3591
3651
  const output = result.stdout + result.stderr;
3592
3652
  const violationMatch = output.match(/Error: Invariant (.*?) is violated/);
3593
3653
  if (violationMatch) {
@@ -3604,18 +3664,13 @@ class DockerRunner {
3604
3664
  if (result.exitCode !== 0 || output.includes("Error:")) {
3605
3665
  return {
3606
3666
  success: false,
3607
- error: this.extractError(output),
3667
+ error: this.extractError(output, result.exitCode, memory),
3608
3668
  output
3609
3669
  };
3610
3670
  }
3611
- const statesMatch = output.match(/(\d+) states generated/);
3612
- const distinctMatch = output.match(/(\d+) distinct states/);
3613
3671
  return {
3614
3672
  success: true,
3615
- stats: {
3616
- statesGenerated: statesMatch?.[1] ? Number.parseInt(statesMatch[1], 10) : 0,
3617
- distinctStates: distinctMatch?.[1] ? Number.parseInt(distinctMatch[1], 10) : 0
3618
- },
3673
+ ...parseTLCStats(output) ?? {},
3619
3674
  output
3620
3675
  };
3621
3676
  }
@@ -3637,7 +3692,14 @@ class DockerRunner {
3637
3692
  }
3638
3693
  return trace;
3639
3694
  }
3640
- extractError(output) {
3695
+ extractError(output, exitCode, memory) {
3696
+ const cap = memory ? ` (--memory ${memory})` : "";
3697
+ if (/java\.lang\.OutOfMemoryError/.test(output)) {
3698
+ return `TLC ran out of heap${cap}. The model is too large for this ceiling — raise verification.memory, lower messages.maxInFlight, or split the subsystem.`;
3699
+ }
3700
+ if (exitCode === 137) {
3701
+ return `TLC was killed for exceeding the container memory limit${cap}. Raise verification.memory, lower messages.maxInFlight, or split the subsystem.`;
3702
+ }
3641
3703
  if (/StatePool\w*\.run/.test(output) && /states\/.*No such file or directory/.test(output)) {
3642
3704
  return "TLC state-pool IO failure (states/.../<n>: No such file or directory). This is a Docker filesystem flake, not a spec error — re-run the verification. See issue #152.";
3643
3705
  }
@@ -3734,8 +3796,36 @@ class DockerRunner {
3734
3796
  });
3735
3797
  }
3736
3798
  }
3737
- var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src/runner", swept = false;
3738
- var init_docker = () => {};
3799
+ function parseTLCStats(output) {
3800
+ const summary = TLC_SUMMARY.exec(output);
3801
+ if (!summary?.[1] || !summary[2] || !summary[3])
3802
+ return;
3803
+ const statesGenerated = Number.parseInt(summary[1], 10);
3804
+ const distinctStates = Number.parseInt(summary[2], 10);
3805
+ const statesLeftOnQueue = Number.parseInt(summary[3], 10);
3806
+ const initial = TLC_INITIAL_STATES.exec(output)?.[1];
3807
+ const initialStates = initial === undefined ? undefined : Number.parseInt(initial, 10);
3808
+ const depth = TLC_SEARCH_DEPTH.exec(output)?.[1];
3809
+ const searchDepth = depth === undefined ? undefined : Number.parseInt(depth, 10);
3810
+ return {
3811
+ stats: {
3812
+ statesGenerated,
3813
+ distinctStates,
3814
+ statesLeftOnQueue,
3815
+ ...initialStates === undefined ? {} : { initialStates },
3816
+ ...searchDepth === undefined ? {} : { searchDepth },
3817
+ exhaustive: statesLeftOnQueue === 0,
3818
+ noActionEnabled: initialStates !== undefined && distinctStates === initialStates
3819
+ }
3820
+ };
3821
+ }
3822
+ var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src/runner", swept = false, TLC_SUMMARY, TLC_INITIAL_STATES, TLC_SEARCH_DEPTH;
3823
+ var init_docker = __esm(() => {
3824
+ init_memory();
3825
+ TLC_SUMMARY = /^(\d+) states generated, (\d+) distinct states found, (\d+) states left on queue/m;
3826
+ TLC_INITIAL_STATES = /Finished computing initial states: (\d+) distinct states generated/;
3827
+ TLC_SEARCH_DEPTH = /The depth of the complete state graph search is (\d+)/;
3828
+ });
3739
3829
 
3740
3830
  // tools/verify/src/cli.ts
3741
3831
  init_expression_validator();
@@ -3778,6 +3868,9 @@ function computeMeshOrPeerSignalFindings(analysis, declaredMeshDocs) {
3778
3868
  return findings;
3779
3869
  }
3780
3870
 
3871
+ // tools/verify/src/analysis/state-space-estimator.ts
3872
+ init_model_constants();
3873
+
3781
3874
  // tools/verify/src/config/types.ts
3782
3875
  function isAdapterConfig(config) {
3783
3876
  return "adapter" in config;
@@ -3790,17 +3883,20 @@ function isLegacyConfig(config) {
3790
3883
  function typedFieldCardinality(name, obj) {
3791
3884
  if (!("type" in obj))
3792
3885
  return null;
3793
- switch (obj.type) {
3886
+ const values = obj["values"];
3887
+ const min = obj["min"];
3888
+ const max = obj["max"];
3889
+ switch (obj["type"]) {
3794
3890
  case "boolean":
3795
3891
  return { name, cardinality: 2, kind: "boolean" };
3796
3892
  case "enum":
3797
- if (Array.isArray(obj.values)) {
3798
- return { name, cardinality: obj.values.length, kind: "enum" };
3893
+ if (Array.isArray(values)) {
3894
+ return { name, cardinality: values.length, kind: "enum" };
3799
3895
  }
3800
3896
  return { name, cardinality: "unbounded", kind: "enum" };
3801
3897
  case "number":
3802
- if (typeof obj.min === "number" && typeof obj.max === "number") {
3803
- return { name, cardinality: obj.max - obj.min + 1, kind: "number" };
3898
+ if (typeof min === "number" && typeof max === "number") {
3899
+ return { name, cardinality: max - min + 1, kind: "number" };
3804
3900
  }
3805
3901
  return { name, cardinality: "unbounded", kind: "number" };
3806
3902
  case "array":
@@ -3812,20 +3908,23 @@ function typedFieldCardinality(name, obj) {
3812
3908
  }
3813
3909
  }
3814
3910
  function legacyFieldCardinality(name, obj) {
3815
- if ("values" in obj && Array.isArray(obj.values)) {
3816
- const base = obj.values.length;
3817
- const extra = obj.abstract === true ? 1 : 0;
3911
+ const values = obj["values"];
3912
+ const abstract = obj["abstract"];
3913
+ const min = obj["min"];
3914
+ const max = obj["max"];
3915
+ if (Array.isArray(values)) {
3916
+ const extra = abstract === true ? 1 : 0;
3818
3917
  return {
3819
3918
  name,
3820
- cardinality: base + extra,
3821
- kind: obj.abstract ? "enum (abstract)" : "enum (values)"
3919
+ cardinality: values.length + extra,
3920
+ kind: abstract === true ? "enum (abstract)" : "enum (values)"
3822
3921
  };
3823
3922
  }
3824
3923
  if ("maxLength" in obj && !("type" in obj)) {
3825
3924
  return { name, cardinality: "unbounded", kind: "array" };
3826
3925
  }
3827
- if ("min" in obj && "max" in obj && typeof obj.min === "number" && typeof obj.max === "number" && !("type" in obj)) {
3828
- return { name, cardinality: obj.max - obj.min + 1, kind: "number" };
3926
+ if (typeof min === "number" && typeof max === "number" && !("type" in obj)) {
3927
+ return { name, cardinality: max - min + 1, kind: "number" };
3829
3928
  }
3830
3929
  return null;
3831
3930
  }
@@ -3839,15 +3938,6 @@ function fieldCardinality(name, value) {
3839
3938
  const obj = value;
3840
3939
  return typedFieldCardinality(name, obj) ?? legacyFieldCardinality(name, obj) ?? { name, cardinality: "unbounded", kind: "unknown" };
3841
3940
  }
3842
- function permutations(n, k) {
3843
- if (k > n)
3844
- return 1;
3845
- let result = 1;
3846
- for (let i = 0;i < k; i++) {
3847
- result *= n - i;
3848
- }
3849
- return result;
3850
- }
3851
3941
  function getHandlerCount(config, analysis) {
3852
3942
  if (isLegacyConfig(config)) {
3853
3943
  const msgs = config.messages;
@@ -3869,11 +3959,11 @@ function getMaxInFlight(config) {
3869
3959
  }
3870
3960
  return 1;
3871
3961
  }
3872
- function getMaxTabs(config) {
3962
+ function getTabSetInput(config) {
3873
3963
  if (isLegacyConfig(config)) {
3874
- return config.messages.maxTabs ?? 1;
3964
+ return config.messages;
3875
3965
  }
3876
- return 1;
3966
+ return {};
3877
3967
  }
3878
3968
  function getFeasibility(states) {
3879
3969
  if (states < 1e5)
@@ -3896,12 +3986,35 @@ function feasibilityLabel(f) {
3896
3986
  return "infeasible (likely won't terminate)";
3897
3987
  }
3898
3988
  }
3899
- function estimateStateSpace(config, analysis) {
3989
+ var OMITTED_VARIABLES = "ports, status, deliveredTo, time and payload";
3990
+ function dominantTerm(e) {
3991
+ const log10 = (n) => n > 0 ? Math.log10(n) : 0;
3992
+ const topology = e.contextCount * TARGET_SET_COUNT * e.tabCount;
3993
+ const terms = [
3994
+ {
3995
+ label: `state fields (${e.fieldProduct} combinations across ${e.contextCount} contexts)`,
3996
+ decades: e.contextCount * log10(e.fieldProduct)
3997
+ },
3998
+ {
3999
+ label: `handler count (${e.handlerCount} handlers at maxInFlight ${e.maxInFlight})`,
4000
+ decades: e.maxInFlight * log10(e.handlerCount)
4001
+ },
4002
+ {
4003
+ label: `send topology (${e.contextCount} sources x ${TARGET_SET_COUNT} target sets x ${e.tabCount} tabs)`,
4004
+ decades: e.maxInFlight * log10(topology)
4005
+ }
4006
+ ];
4007
+ return terms.reduce((a, b) => b.decades > a.decades ? b : a);
4008
+ }
4009
+ function estimateForScope(config, scope) {
3900
4010
  const state = config.state;
3901
4011
  const fields = [];
3902
4012
  const warnings = [];
3903
4013
  const suggestions = [];
4014
+ const included = scope.stateFields ? new Set(scope.stateFields) : undefined;
3904
4015
  for (const [name, value] of Object.entries(state)) {
4016
+ if (included && !included.has(name))
4017
+ continue;
3905
4018
  fields.push(fieldCardinality(name, value));
3906
4019
  }
3907
4020
  const unboundedFields = fields.filter((f) => f.cardinality === "unbounded");
@@ -3911,33 +4024,47 @@ function estimateStateSpace(config, analysis) {
3911
4024
  warnings.push(`${unboundedFields.length} field(s) have unbounded domains: ${unboundedFields.map((f) => f.name).join(", ")}`);
3912
4025
  suggestions.push(`Fields ${unboundedFields.map((f) => f.name).join(", ")} have unbounded domains — their actual impact depends on handler logic`);
3913
4026
  }
3914
- const handlerCount = getHandlerCount(config, analysis);
3915
- const maxInFlight = getMaxInFlight(config);
3916
- const maxTabs = getMaxTabs(config);
3917
- const contextCount = maxTabs + 1;
4027
+ const { handlerCount, maxInFlight } = scope;
4028
+ const tabSetInput = getTabSetInput(config);
4029
+ const tabCount = generatedTabCount(tabSetInput);
4030
+ const contextCount = CONTEXT_COUNT;
3918
4031
  const totalStateSpace = fieldProduct ** contextCount;
3919
- const interleavingFactor = permutations(handlerCount, maxInFlight);
4032
+ const sendBranching = sendBranchingFactor(handlerCount, tabCount);
4033
+ const interleavingFactor = sendBranching ** maxInFlight;
3920
4034
  const estimatedStates = totalStateSpace * interleavingFactor;
3921
4035
  const feasibility = getFeasibility(estimatedStates);
4036
+ warnings.push(`Lower bound: the generated spec also carries ${OMITTED_VARIABLES}, none of which this figure models.`);
4037
+ if (tabSetInput.tabSymmetry) {
4038
+ warnings.push("tabSymmetry is enabled — TLC's symmetry reduction cuts the reachable set, so this is an upper bound on the tab dimension.");
4039
+ }
4040
+ const dominant = dominantTerm({
4041
+ fieldProduct,
4042
+ handlerCount,
4043
+ contextCount,
4044
+ tabCount,
4045
+ maxInFlight
4046
+ });
4047
+ suggestions.push(`Dominant term: ${dominant.label} — ~10^${dominant.decades.toFixed(1)} of ~10^${Math.log10(Math.max(estimatedStates, 1)).toFixed(1)}`);
4048
+ if (maxInFlight > 1) {
4049
+ suggestions.push(`maxInFlight ${maxInFlight} → ${maxInFlight - 1} divides the estimate by ${sendBranching.toLocaleString()}x (one fewer send to branch over)`);
4050
+ }
3922
4051
  if (handlerCount > 15) {
3923
- suggestions.push("Consider splitting into subsystems");
4052
+ suggestions.push(`${handlerCount} handlers enter the estimate as ${handlerCount}^${maxInFlight} — splitting into subsystems is the cheapest cut`);
3924
4053
  }
3925
4054
  for (const f of boundedFields) {
3926
4055
  if (f.cardinality > 50) {
3927
- suggestions.push(`Consider reducing bounds for field "${f.name}" (${f.cardinality} values)`);
4056
+ suggestions.push(`Field "${f.name}" (${f.cardinality} values) is raised to the power of ${contextCount} contexts — reducing its bounds compounds`);
3928
4057
  }
3929
4058
  }
3930
- if (maxInFlight > 2) {
3931
- const reducedInterleaving = permutations(handlerCount, 2);
3932
- const reduction = reducedInterleaving > 0 ? Math.round(interleavingFactor / reducedInterleaving) : 1;
3933
- suggestions.push(`Reducing maxInFlight from ${maxInFlight} to 2 would reduce interleaving by ~${reduction}x`);
3934
- }
3935
4059
  return {
4060
+ ...scope.name === undefined ? {} : { subsystem: scope.name },
3936
4061
  fields,
3937
4062
  fieldProduct,
3938
4063
  handlerCount,
3939
4064
  maxInFlight,
3940
4065
  contextCount,
4066
+ tabCount,
4067
+ sendBranching,
3941
4068
  totalStateSpace,
3942
4069
  interleavingFactor,
3943
4070
  estimatedStates,
@@ -3946,6 +4073,24 @@ function estimateStateSpace(config, analysis) {
3946
4073
  suggestions
3947
4074
  };
3948
4075
  }
4076
+ function estimateStateSpace(config, analysis) {
4077
+ return estimateForScope(config, {
4078
+ handlerCount: getHandlerCount(config, analysis),
4079
+ maxInFlight: getMaxInFlight(config)
4080
+ });
4081
+ }
4082
+ function estimateSubsystems(config) {
4083
+ const subsystems = config.subsystems;
4084
+ if (!subsystems)
4085
+ return [];
4086
+ const topLevelMaxInFlight = getMaxInFlight(config);
4087
+ return Object.entries(subsystems).map(([name, sub]) => estimateForScope(config, {
4088
+ name,
4089
+ stateFields: sub.state,
4090
+ handlerCount: sub.handlers.length,
4091
+ maxInFlight: sub.bounds?.maxInFlight ?? topLevelMaxInFlight
4092
+ }));
4093
+ }
3949
4094
 
3950
4095
  // tools/verify/src/codegen/config.ts
3951
4096
  class ConfigGenerator {
@@ -4402,6 +4547,7 @@ function generateConfig(analysis, projectType = "chrome-extension") {
4402
4547
  }
4403
4548
 
4404
4549
  // tools/verify/src/config/parser.ts
4550
+ init_memory();
4405
4551
  import * as fs from "node:fs";
4406
4552
  import * as path from "node:path";
4407
4553
 
@@ -4875,6 +5021,31 @@ class ConfigValidator {
4875
5021
  });
4876
5022
  }
4877
5023
  }
5024
+ this.validateMemory(verification.memory);
5025
+ }
5026
+ validateMemory(memory) {
5027
+ if (memory === undefined)
5028
+ return;
5029
+ const bytes = parseMemoryBytes(memory);
5030
+ if (bytes === null) {
5031
+ this.issues.push({
5032
+ type: "invalid_value",
5033
+ severity: "error",
5034
+ field: "verification.memory",
5035
+ message: `"${memory}" is not a docker-style memory size`,
5036
+ suggestion: 'Use a number with an optional b/k/m/g suffix, e.g. "8g"'
5037
+ });
5038
+ return;
5039
+ }
5040
+ if (bytes < MIN_TLC_MEMORY_BYTES) {
5041
+ this.issues.push({
5042
+ type: "invalid_value",
5043
+ severity: "error",
5044
+ field: "verification.memory",
5045
+ message: `${memory} is below the ${MIN_TLC_MEMORY} minimum TLC can run in`,
5046
+ suggestion: `Use ${MIN_TLC_MEMORY} or more`
5047
+ });
5048
+ }
4878
5049
  }
4879
5050
  validateTier2Optimizations(tier2) {
4880
5051
  if (tier2.temporalConstraints) {
@@ -8427,6 +8598,9 @@ async function analyzeCodebase(options) {
8427
8598
  const extractor = new TypeExtractor(options.tsConfigPath);
8428
8599
  return extractor.analyzeCodebase(options.stateFilePath);
8429
8600
  }
8601
+ // tools/verify/src/cli.ts
8602
+ init_memory();
8603
+
8430
8604
  // tools/verify/src/runner/mesh-seed.ts
8431
8605
  function meshSeedCfg(baseCfg, opts) {
8432
8606
  if (!opts.disableFix)
@@ -8612,11 +8786,30 @@ async function estimateCommand() {
8612
8786
  if (exprValidation.warnings.length > 0) {
8613
8787
  displayExpressionWarnings(exprValidation);
8614
8788
  }
8615
- const estimate = estimateStateSpace(typedConfig, typedAnalysis);
8616
- displayEstimate(estimate);
8789
+ const perSubsystem = estimateSubsystems(typedConfig);
8790
+ if (perSubsystem.length > 0) {
8791
+ for (const estimate of perSubsystem) {
8792
+ displayEstimate(estimate);
8793
+ }
8794
+ displaySubsystemEstimateSummary(perSubsystem);
8795
+ return;
8796
+ }
8797
+ displayEstimate(estimateStateSpace(typedConfig, typedAnalysis));
8798
+ }
8799
+ function displaySubsystemEstimateSummary(estimates) {
8800
+ console.log(color(`Per-subsystem summary:
8801
+ `, COLORS.blue));
8802
+ for (const e of estimates) {
8803
+ const name = (e.subsystem ?? "?").padEnd(20);
8804
+ const states = `~${e.estimatedStates.toLocaleString()}`.padEnd(20);
8805
+ const feasColor = e.feasibility === "trivial" || e.feasibility === "feasible" ? COLORS.green : e.feasibility === "slow" ? COLORS.yellow : COLORS.red;
8806
+ console.log(` ${name} ${states} ${color(e.feasibility, feasColor)}`);
8807
+ }
8808
+ console.log();
8617
8809
  }
8618
8810
  function displayEstimate(estimate) {
8619
- console.log(color(`State space estimate:
8811
+ const heading = estimate.subsystem ? `State space estimate — subsystem ${estimate.subsystem}:` : "State space estimate:";
8812
+ console.log(color(`${heading}
8620
8813
  `, COLORS.blue));
8621
8814
  console.log(color(" Fields:", COLORS.blue));
8622
8815
  for (const field of estimate.fields) {
@@ -8629,8 +8822,10 @@ function displayEstimate(estimate) {
8629
8822
  console.log(` Field combinations: ${color(String(estimate.fieldProduct), COLORS.green)}`);
8630
8823
  console.log(` Handlers: ${estimate.handlerCount}`);
8631
8824
  console.log(` Max in-flight: ${estimate.maxInFlight}`);
8632
- console.log(` Contexts: ${estimate.contextCount} (${estimate.contextCount - 1} tab${estimate.contextCount - 1 === 1 ? "" : "s"} + background)`);
8633
- console.log(` Interleaving factor: ${estimate.interleavingFactor}`);
8825
+ console.log(` Contexts: ${estimate.contextCount} (fixed by the generated .cfg)`);
8826
+ console.log(` Tabs: ${estimate.tabCount}`);
8827
+ console.log(` State across contexts: ${estimate.fieldProduct}^${estimate.contextCount} = ${estimate.totalStateSpace.toLocaleString()}`);
8828
+ console.log(` Send branching: ${estimate.sendBranching.toLocaleString()} per message (^${estimate.maxInFlight} = ${estimate.interleavingFactor.toLocaleString()})`);
8634
8829
  console.log();
8635
8830
  console.log(` Estimated states: ${color(`~${estimate.estimatedStates.toLocaleString()}`, COLORS.green)}`);
8636
8831
  const feasColor = estimate.feasibility === "trivial" || estimate.feasibility === "feasible" ? COLORS.green : estimate.feasibility === "slow" ? COLORS.yellow : COLORS.red;
@@ -8854,6 +9049,9 @@ function getTimeout(config) {
8854
9049
  function getWorkers(config) {
8855
9050
  return config.verification?.workers ?? 1;
8856
9051
  }
9052
+ function getMemory(config) {
9053
+ return config.verification?.memory;
9054
+ }
8857
9055
  function getMaxDepth(config) {
8858
9056
  if ("tier2" in config && config.tier2?.boundedExploration?.maxDepth) {
8859
9057
  return config.tier2.boundedExploration.maxDepth;
@@ -8927,6 +9125,7 @@ async function runMonolithicVerification(config, analysis) {
8927
9125
  const timeoutSeconds = getTimeout(typedConfig);
8928
9126
  const workers = getWorkers(typedConfig);
8929
9127
  const maxDepth = getMaxDepth(typedConfig);
9128
+ const memory = getMemory(typedConfig);
8930
9129
  console.log(color("⚙️ Running TLC model checker...", COLORS.blue));
8931
9130
  if (timeoutSeconds === 0) {
8932
9131
  console.log(color(" No timeout set - will run until completion", COLORS.gray));
@@ -8938,13 +9137,15 @@ async function runMonolithicVerification(config, analysis) {
8938
9137
  if (maxDepth !== undefined) {
8939
9138
  console.log(color(` Max depth: ${maxDepth}`, COLORS.gray));
8940
9139
  }
9140
+ console.log(color(` Memory: ${memory ?? DEFAULT_TLC_MEMORY}`, COLORS.gray));
8941
9141
  console.log();
8942
9142
  const result = await docker.runTLC(specPath, {
8943
9143
  workers,
8944
9144
  timeout: timeoutSeconds > 0 ? timeoutSeconds * 1000 : undefined,
8945
- maxDepth
9145
+ maxDepth,
9146
+ memory
8946
9147
  });
8947
- const meshSeed = await runMeshSeedGuard(docker, specDir, config);
9148
+ const meshSeed = await runMeshSeedGuard(docker, specDir, config, memory);
8948
9149
  if (meshSeed && !meshSeed.success) {
8949
9150
  console.log(color(`
8950
9151
  ❌ Mesh seed-race guard failed (polly#113 / polly#114)
@@ -9011,6 +9212,7 @@ async function runSubsystemVerification(config, analysis) {
9011
9212
  const timeoutSeconds = getTimeout(config);
9012
9213
  const workers = getWorkers(config);
9013
9214
  const maxDepth = getMaxDepth(config);
9215
+ const memory = getMemory(config);
9014
9216
  await Promise.resolve().then(() => init_tla());
9015
9217
  const results = [];
9016
9218
  const customTLAPaths = getCustomTLAPaths(config);
@@ -9036,7 +9238,8 @@ async function runSubsystemVerification(config, analysis) {
9036
9238
  const result = await docker.runTLC(specPath, {
9037
9239
  workers,
9038
9240
  timeout: timeoutSeconds > 0 ? timeoutSeconds * 1000 : undefined,
9039
- maxDepth
9241
+ maxDepth,
9242
+ memory
9040
9243
  });
9041
9244
  const elapsed = (Date.now() - startTime) / 1000;
9042
9245
  results.push({
@@ -9044,9 +9247,8 @@ async function runSubsystemVerification(config, analysis) {
9044
9247
  success: result.success,
9045
9248
  handlerCount: sub.handlers.length,
9046
9249
  ensuresCount,
9047
- stateCount: result.stats?.distinctStates ?? 0,
9048
9250
  elapsed,
9049
- stats: result.stats,
9251
+ ...result.stats === undefined ? {} : { stats: result.stats },
9050
9252
  error: result.error
9051
9253
  });
9052
9254
  if (result.success) {
@@ -9065,6 +9267,14 @@ async function runSubsystemVerification(config, analysis) {
9065
9267
  displayCompositionalReport(results, interference.valid);
9066
9268
  }
9067
9269
  var DEFAULT_WITNESS_TIMEOUT_MS = 180000;
9270
+ function witnessExplorationNote(stats) {
9271
+ if (!stats)
9272
+ return "TLC printed no summary line — exploration incomplete, verdict unsound";
9273
+ if (!stats.exhaustive) {
9274
+ return `${stats.distinctStates} distinct states explored, ${stats.statesLeftOnQueue} left on queue — not exhaustive`;
9275
+ }
9276
+ return `${stats.distinctStates} distinct states explored`;
9277
+ }
9068
9278
  async function globAbsolute(cwd, pattern) {
9069
9279
  const out = [];
9070
9280
  for await (const f of new Bun.Glob(pattern).scan({ cwd, absolute: true, onlyFiles: true })) {
@@ -9097,6 +9307,7 @@ async function runWitnessVerification(config) {
9097
9307
  const timeoutSeconds = getTimeout(config);
9098
9308
  const timeout = timeoutSeconds > 0 ? timeoutSeconds * 1000 : DEFAULT_WITNESS_TIMEOUT_MS;
9099
9309
  const workers = getWorkers(config);
9310
+ const memory = getMemory(config);
9100
9311
  const results = [];
9101
9312
  let idx = 0;
9102
9313
  for (const w of witnesses) {
@@ -9183,7 +9394,7 @@ async function runWitnessVerification(config) {
9183
9394
  console.log(color(` ${subsystem} ⊨ ${w.predicate}`, COLORS.gray));
9184
9395
  let tlc;
9185
9396
  try {
9186
- tlc = await docker.runTLC(witnessTla, { workers, timeout });
9397
+ tlc = await docker.runTLC(witnessTla, { workers, timeout, memory });
9187
9398
  } catch (err) {
9188
9399
  const msg = err instanceof Error ? err.message : String(err);
9189
9400
  results.push({
@@ -9212,7 +9423,7 @@ async function runWitnessVerification(config) {
9212
9423
  continue;
9213
9424
  }
9214
9425
  const verdict = witnessVerdict(polarity, reachable);
9215
- const note = reachable ? undefined : `${tlc.stats?.distinctStates ?? 0} distinct states explored`;
9426
+ const note = reachable ? undefined : witnessExplorationNote(tlc.stats);
9216
9427
  results.push({
9217
9428
  id,
9218
9429
  status: verdict.status,
@@ -9278,6 +9489,53 @@ function displayEnsuresSummary(results) {
9278
9489
  console.log(color(" A property only fires at states where its handler is enabled — raise", COLORS.gray));
9279
9490
  console.log(color(" bounds.maxInFlight on a subsystem to reach multi-step-reachable handlers.", COLORS.gray));
9280
9491
  }
9492
+ function stateColumn(stats) {
9493
+ if (!stats)
9494
+ return "? states";
9495
+ if (!stats.exhaustive)
9496
+ return `${stats.distinctStates}+ states`;
9497
+ return `${stats.distinctStates} states`;
9498
+ }
9499
+ function displayTLCStats(stats) {
9500
+ if (!stats) {
9501
+ console.log(color(" States explored: unknown (TLC printed no summary line)", COLORS.yellow));
9502
+ return;
9503
+ }
9504
+ console.log(color(` States explored: ${stats.statesGenerated}`, COLORS.gray));
9505
+ console.log(color(` Distinct states: ${stats.distinctStates}`, COLORS.gray));
9506
+ if (stats.searchDepth !== undefined) {
9507
+ console.log(color(` Search depth: ${stats.searchDepth}`, COLORS.gray));
9508
+ }
9509
+ if (!stats.exhaustive) {
9510
+ console.log(color(` ⚠ incomplete: ${stats.statesLeftOnQueue} states left on queue — the model was not exhausted`, COLORS.yellow));
9511
+ }
9512
+ if (stats.noActionEnabled) {
9513
+ console.log(color(` ⚠ no action was ever enabled: distinct states equal the ${stats.initialStates} initial states`, COLORS.yellow));
9514
+ }
9515
+ }
9516
+ function displayIncompleteSubsystems(results) {
9517
+ const notes = [];
9518
+ for (const r of results) {
9519
+ if (!r.success)
9520
+ continue;
9521
+ if (!r.stats) {
9522
+ notes.push(`${r.name}: TLC printed no summary line — state count unknown`);
9523
+ continue;
9524
+ }
9525
+ if (!r.stats.exhaustive) {
9526
+ notes.push(`${r.name}: ${r.stats.statesLeftOnQueue} states left on queue — not exhausted`);
9527
+ }
9528
+ if (r.stats.noActionEnabled) {
9529
+ notes.push(`${r.name}: no action was ever enabled — the ${r.stats.initialStates} states explored are the initial states`);
9530
+ }
9531
+ }
9532
+ if (notes.length === 0)
9533
+ return;
9534
+ console.log();
9535
+ for (const note of notes) {
9536
+ console.log(color(` ⚠ ${note}`, COLORS.yellow));
9537
+ }
9538
+ }
9281
9539
  function displayCompositionalReport(results, nonInterferenceValid) {
9282
9540
  console.log(color(`Subsystem verification results:
9283
9541
  `, COLORS.blue));
@@ -9286,10 +9544,11 @@ function displayCompositionalReport(results, nonInterferenceValid) {
9286
9544
  const name = r.name.padEnd(20);
9287
9545
  const handlers = `${r.handlerCount} handler${r.handlerCount === 1 ? "" : "s"}`;
9288
9546
  const ensures = `${r.ensuresCount} ensures`;
9289
- const states = `${r.stateCount} states`;
9547
+ const states = stateColumn(r.stats);
9290
9548
  const time = `${r.elapsed.toFixed(1)}s`;
9291
9549
  console.log(` ${status} ${name} ${handlers.padEnd(14)} ${ensures.padEnd(12)} ${states.padEnd(14)} ${time}`);
9292
9550
  }
9551
+ displayIncompleteSubsystems(results);
9293
9552
  displayEnsuresSummary(results);
9294
9553
  console.log();
9295
9554
  const nonIntLabel = nonInterferenceValid ? color("✓ verified (no cross-subsystem state writes)", COLORS.green) : color("⚠ violations detected", COLORS.yellow);
@@ -9387,7 +9646,7 @@ function findMeshSeedSpecDir() {
9387
9646
  }
9388
9647
  return null;
9389
9648
  }
9390
- async function runMeshSeedGuard(docker, specDir, config) {
9649
+ async function runMeshSeedGuard(docker, specDir, config, memory) {
9391
9650
  if (getMeshDocIds(config).length === 0)
9392
9651
  return;
9393
9652
  const sourceDir = findMeshSeedSpecDir();
@@ -9400,7 +9659,7 @@ async function runMeshSeedGuard(docker, specDir, config) {
9400
9659
  const baseCfg = fs4.readFileSync(path5.join(sourceDir, "MeshSeed.cfg"), "utf8");
9401
9660
  fs4.writeFileSync(path5.join(specDir, "MeshSeed.cfg"), meshSeedCfg(baseCfg, { disableFix: fixDisabled }));
9402
9661
  console.log(color(`⚙️ Running mesh seed-race guard (MeshSeed.tla, SeedDeterministic = ${fixDisabled ? "FALSE" : "TRUE"})...`, COLORS.blue));
9403
- return docker.runTLC(path5.join(specDir, "MeshSeed.tla"), { workers: 1 });
9662
+ return docker.runTLC(path5.join(specDir, "MeshSeed.tla"), { workers: 1, memory });
9404
9663
  }
9405
9664
  async function setupDocker() {
9406
9665
  await Promise.resolve().then(() => init_docker());
@@ -9460,8 +9719,7 @@ function displayVerificationResults(result, specDir) {
9460
9719
  console.log(color(`✅ Verification passed!
9461
9720
  `, COLORS.green));
9462
9721
  console.log(color("Statistics:", COLORS.blue));
9463
- console.log(color(` States explored: ${result.stats?.statesGenerated || 0}`, COLORS.gray));
9464
- console.log(color(` Distinct states: ${result.stats?.distinctStates || 0}`, COLORS.gray));
9722
+ displayTLCStats(result.stats);
9465
9723
  console.log();
9466
9724
  return;
9467
9725
  }
@@ -9574,4 +9832,4 @@ main().catch((error) => {
9574
9832
  process.exit(1);
9575
9833
  });
9576
9834
 
9577
- //# debugId=92CAFBFCF98F237564756E2164756E21
9835
+ //# debugId=10A3CEC0EDCA40C864756E2164756E21