@fairfox/polly 0.37.0 → 0.38.1

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.
@@ -401,6 +401,32 @@ async function generateTLA(config, analysis) {
401
401
  const generator = new TLAGenerator;
402
402
  return await generator.generate(config, analysis);
403
403
  }
404
+ function applySubsystemBounds(messages, subsystem, handlerNames) {
405
+ if (messages.perMessageBounds) {
406
+ const filtered = {};
407
+ for (const [msg, bound] of Object.entries(messages.perMessageBounds)) {
408
+ if (handlerNames.has(msg)) {
409
+ filtered[msg] = bound;
410
+ }
411
+ }
412
+ messages.perMessageBounds = filtered;
413
+ }
414
+ const override = subsystem.bounds;
415
+ if (!override)
416
+ return;
417
+ if (override.maxInFlight !== undefined) {
418
+ messages.maxInFlight = override.maxInFlight;
419
+ }
420
+ if (override.perMessageBounds) {
421
+ const merged = { ...messages.perMessageBounds ?? {} };
422
+ for (const [msg, bound] of Object.entries(override.perMessageBounds)) {
423
+ if (handlerNames.has(msg)) {
424
+ merged[msg] = bound;
425
+ }
426
+ }
427
+ messages.perMessageBounds = merged;
428
+ }
429
+ }
404
430
  async function generateSubsystemTLA(_subsystemName, subsystem, config, analysis) {
405
431
  const stateFields = new Set(subsystem.state);
406
432
  const handlerNames = new Set(subsystem.handlers);
@@ -415,15 +441,7 @@ async function generateSubsystemTLA(_subsystemName, subsystem, config, analysis)
415
441
  include: subsystem.handlers
416
442
  };
417
443
  filteredMessages.exclude = undefined;
418
- if (filteredMessages.perMessageBounds) {
419
- const filteredBounds = {};
420
- for (const [msg, bound] of Object.entries(filteredMessages.perMessageBounds)) {
421
- if (handlerNames.has(msg)) {
422
- filteredBounds[msg] = bound;
423
- }
424
- }
425
- filteredMessages.perMessageBounds = filteredBounds;
426
- }
444
+ applySubsystemBounds(filteredMessages, subsystem, handlerNames);
427
445
  const filteredConfig = {
428
446
  ...config,
429
447
  state: filteredState,
@@ -3613,7 +3631,7 @@ class ConfigValidator {
3613
3631
  this.validateTier2Optimizations(config.tier2);
3614
3632
  }
3615
3633
  if (config.subsystems) {
3616
- this.validateSubsystems(config.subsystems, config.state);
3634
+ this.validateSubsystems(config.subsystems, config.state, config.messages);
3617
3635
  }
3618
3636
  }
3619
3637
  findNullPlaceholders(obj, path2) {
@@ -3931,7 +3949,7 @@ class ConfigValidator {
3931
3949
  }
3932
3950
  }
3933
3951
  }
3934
- validateSubsystems(subsystems, stateConfig) {
3952
+ validateSubsystems(subsystems, stateConfig, messages) {
3935
3953
  const stateFieldNames = Object.keys(stateConfig);
3936
3954
  const allAssignedHandlers = new Map;
3937
3955
  const allAssignedFields = new Map;
@@ -3991,6 +4009,69 @@ class ConfigValidator {
3991
4009
  suggestion: "Add at least one state field to the subsystem"
3992
4010
  });
3993
4011
  }
4012
+ if (subsystem.bounds) {
4013
+ this.validateSubsystemBounds(subsystemName, subsystem, messages);
4014
+ }
4015
+ }
4016
+ }
4017
+ validateSubsystemBounds(subsystemName, subsystem, messages) {
4018
+ const bounds = subsystem.bounds;
4019
+ if (!bounds)
4020
+ return;
4021
+ const effectiveMaxInFlight = bounds.maxInFlight === undefined ? messages.maxInFlight ?? 3 : bounds.maxInFlight;
4022
+ if (bounds.maxInFlight !== undefined && bounds.maxInFlight < 1) {
4023
+ this.issues.push({
4024
+ type: "invalid_value",
4025
+ severity: "error",
4026
+ field: `subsystems.${subsystemName}.bounds.maxInFlight`,
4027
+ message: `bounds.maxInFlight must be at least 1, got ${bounds.maxInFlight}`,
4028
+ suggestion: "Use 1 to keep the global value, or 2+ to exercise multi-step ensures"
4029
+ });
4030
+ }
4031
+ if (bounds.maxInFlight !== undefined && bounds.maxInFlight > 20) {
4032
+ this.issues.push({
4033
+ type: "unrealistic_bound",
4034
+ severity: "warning",
4035
+ field: `subsystems.${subsystemName}.bounds.maxInFlight`,
4036
+ message: `Very high bounds.maxInFlight (${bounds.maxInFlight}) will slow this subsystem's verification significantly`,
4037
+ suggestion: "Use 2-4 for most multi-step ensures cases"
4038
+ });
4039
+ }
4040
+ if (bounds.perMessageBounds) {
4041
+ const handlerSet = new Set(subsystem.handlers);
4042
+ for (const [msg, bound] of Object.entries(bounds.perMessageBounds)) {
4043
+ this.validatePerMessageBound(subsystemName, msg, bound, effectiveMaxInFlight, handlerSet);
4044
+ }
4045
+ }
4046
+ }
4047
+ validatePerMessageBound(subsystemName, msg, bound, effectiveMaxInFlight, handlerSet) {
4048
+ const field = `subsystems.${subsystemName}.bounds.perMessageBounds.${msg}`;
4049
+ if (bound < 1) {
4050
+ this.issues.push({
4051
+ type: "invalid_value",
4052
+ severity: "error",
4053
+ field,
4054
+ message: `perMessageBounds[${msg}] must be at least 1, got ${bound}`,
4055
+ suggestion: "Remove the entry to inherit from the global bound, or set 1+"
4056
+ });
4057
+ }
4058
+ if (bound > effectiveMaxInFlight) {
4059
+ this.issues.push({
4060
+ type: "invalid_value",
4061
+ severity: "error",
4062
+ field,
4063
+ message: `perMessageBounds[${msg}] = ${bound} exceeds effective maxInFlight = ${effectiveMaxInFlight}; the bound is unreachable and ${msg} will not be explored`,
4064
+ suggestion: `Lower perMessageBounds[${msg}] to ≤ ${effectiveMaxInFlight}, or raise bounds.maxInFlight`
4065
+ });
4066
+ }
4067
+ if (!handlerSet.has(msg)) {
4068
+ this.issues.push({
4069
+ type: "invalid_value",
4070
+ severity: "warning",
4071
+ field,
4072
+ message: `perMessageBounds[${msg}] is set on subsystem "${subsystemName}" but ${msg} is not in its handlers list; the override will be silently dropped`,
4073
+ suggestion: `Move ${msg} into subsystems.${subsystemName}.handlers, or remove the override`
4074
+ });
3994
4075
  }
3995
4076
  }
3996
4077
  }
@@ -7435,6 +7516,7 @@ async function runSubsystemVerification(config, analysis) {
7435
7516
  const startTime = Date.now();
7436
7517
  console.log(color(`⚙️ Verifying subsystem: ${name}...`, COLORS.blue));
7437
7518
  const { spec, cfg } = await generateSubsystemTLA2(name, sub, config, analysis);
7519
+ const ensuresCount = (spec.match(/^EnsuresAfter_\w+ ==/gm) ?? []).length;
7438
7520
  const specDir = path4.join(process.cwd(), "specs", "tla", "generated", name);
7439
7521
  if (!fs4.existsSync(specDir)) {
7440
7522
  fs4.mkdirSync(specDir, { recursive: true });
@@ -7454,6 +7536,7 @@ async function runSubsystemVerification(config, analysis) {
7454
7536
  name,
7455
7537
  success: result.success,
7456
7538
  handlerCount: sub.handlers.length,
7539
+ ensuresCount,
7457
7540
  stateCount: result.stats?.distinctStates ?? 0,
7458
7541
  elapsed,
7459
7542
  stats: result.stats,
@@ -7474,6 +7557,16 @@ async function runSubsystemVerification(config, analysis) {
7474
7557
  console.log();
7475
7558
  displayCompositionalReport(results, interference.valid);
7476
7559
  }
7560
+ function displayEnsuresSummary(results) {
7561
+ const totalEnsures = results.reduce((sum, r) => sum + r.ensuresCount, 0);
7562
+ if (totalEnsures === 0)
7563
+ return;
7564
+ const subsystemWord = `subsystem${results.length === 1 ? "" : "s"}`;
7565
+ console.log();
7566
+ console.log(color(` ${totalEnsures} ensures step-properties registered across ${results.length} ${subsystemWord}.`, COLORS.gray));
7567
+ console.log(color(" A property only fires at states where its handler is enabled — raise", COLORS.gray));
7568
+ console.log(color(" bounds.maxInFlight on a subsystem to reach multi-step-reachable handlers.", COLORS.gray));
7569
+ }
7477
7570
  function displayCompositionalReport(results, nonInterferenceValid) {
7478
7571
  console.log(color(`Subsystem verification results:
7479
7572
  `, COLORS.blue));
@@ -7481,10 +7574,12 @@ function displayCompositionalReport(results, nonInterferenceValid) {
7481
7574
  const status = r.success ? color("✓", COLORS.green) : color("✗", COLORS.red);
7482
7575
  const name = r.name.padEnd(20);
7483
7576
  const handlers = `${r.handlerCount} handler${r.handlerCount === 1 ? "" : "s"}`;
7577
+ const ensures = `${r.ensuresCount} ensures`;
7484
7578
  const states = `${r.stateCount} states`;
7485
7579
  const time = `${r.elapsed.toFixed(1)}s`;
7486
- console.log(` ${status} ${name} ${handlers.padEnd(14)} ${states.padEnd(14)} ${time}`);
7580
+ console.log(` ${status} ${name} ${handlers.padEnd(14)} ${ensures.padEnd(12)} ${states.padEnd(14)} ${time}`);
7487
7581
  }
7582
+ displayEnsuresSummary(results);
7488
7583
  console.log();
7489
7584
  const nonIntLabel = nonInterferenceValid ? color("✓ verified (no cross-subsystem state writes)", COLORS.green) : color("⚠ violations detected", COLORS.yellow);
7490
7585
  console.log(` Non-interference: ${nonIntLabel}`);
@@ -7724,4 +7819,4 @@ main().catch((error) => {
7724
7819
  process.exit(1);
7725
7820
  });
7726
7821
 
7727
- //# debugId=550B2383F1F1827964756E2164756E21
7822
+ //# debugId=17E54E7016FB5CE064756E2164756E21