@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
|
-
|
|
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=
|
|
7822
|
+
//# debugId=17E54E7016FB5CE064756E2164756E21
|