fix(firmware): stop recommending a raise at the staging budget ceiling (#6971)
The staging refusal told callers to increase max_output_files or max_output_bytes within its schema maximum. At the schema ceiling that recovery does not exist, so the refusal was unactionable exactly where the least room remains. firmwareStagingBudgetRemediation now branches on the selected value against the schema maximum: below the ceiling it names the room and the control, at the ceiling it states that a further raise is unsupported and points at a smaller firmware byte range. The schema maxima move into named constants so the advice and the accepted range cannot drift apart. Verified with a module test for both budgets at the ceiling and one below it (red on the previous single-form remediation), plus the existing firmware suites.
This commit is contained in:
1 parent
73320bb2c9
commit
2e86294c86
3 files changed
+90
-3
No files matched your search
@@ -9,6 +9,10 @@ const range = z.strictObject({ offset: bytes, length: bytes.positive() });
|
||||
export const DEFAULT_FIRMWARE_ANALYSIS_TIMEOUT_MS = 120_000;
|
||||
/** Largest delay supported without overflowing Node timers. */
|
||||
export const MAX_FIRMWARE_TIMEOUT_MS = 2_147_483_647;
|
||||
/** Largest staging file-byte budget the schema accepts. */
|
||||
export const MAX_FIRMWARE_STAGING_BYTES = 512 * 1024 * 1024;
|
||||
/** Largest staging entry budget the schema accepts. */
|
||||
export const MAX_FIRMWARE_STAGING_ENTRIES = 100_000;
|
||||
const timeout = z.number().int().min(1).max(MAX_FIRMWARE_TIMEOUT_MS);
|
||||
const target = {
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
@@ -43,7 +47,7 @@ export const firmwareInputSchemas = {
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(512 * 1024 * 1024)
|
||||
.max(MAX_FIRMWARE_STAGING_BYTES)
|
||||
.default(256 * 1024 * 1024)
|
||||
.describe(
|
||||
"Staging file-byte budget, checked during extraction; transient overshoot is possible",
|
||||
@@ -52,7 +56,7 @@ export const firmwareInputSchemas = {
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(100_000)
|
||||
.max(MAX_FIRMWARE_STAGING_ENTRIES)
|
||||
.default(10_000)
|
||||
.describe("Staging entry budget, including directories and links"),
|
||||
publication_timeout_ms: timeout
|
||||
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import type { FirmwareRequest } from "../domain/firmware/firmwareAnalysis.js";
|
||||
import {
|
||||
MAX_FIRMWARE_STAGING_BYTES,
|
||||
MAX_FIRMWARE_STAGING_ENTRIES,
|
||||
} from "../domain/firmware/firmwareAnalysis.js";
|
||||
import { absoluteCallerPath } from "../filesystem/callerPath.js";
|
||||
import { readLinuxResourceLimits } from "../process/LinuxResourceLimits.js";
|
||||
import { readBoundedFileBytes } from "../process/BoundedFileBytes.js";
|
||||
@@ -442,6 +446,27 @@ export const inventoryFirmwareOutput = async (
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Recovery for one staging budget refusal. At the schema ceiling a larger
|
||||
* selection is impossible, so that case must not recommend raising the value.
|
||||
*/
|
||||
export const firmwareStagingBudgetRemediation = (
|
||||
budget: "max_output_files" | "max_output_bytes",
|
||||
selectedLimit: number,
|
||||
): string => {
|
||||
const maximum =
|
||||
budget === "max_output_files"
|
||||
? MAX_FIRMWARE_STAGING_ENTRIES
|
||||
: MAX_FIRMWARE_STAGING_BYTES;
|
||||
const description =
|
||||
budget === "max_output_files"
|
||||
? "staging entry budget, which counts extracted directories and links"
|
||||
: "staging file-byte budget, checked during extraction";
|
||||
if (selectedLimit < maximum)
|
||||
return `The ${description} was exceeded (${budget}=${String(selectedLimit)}; supported maximum ${String(maximum)}). Increase ${budget} up to ${String(maximum)} if the larger extraction is required, or select a materially narrower firmware byte range. analysis_timeout_ms does not raise this staging allowance.`;
|
||||
return `The ${description} reached its supported maximum (${budget}=${String(selectedLimit)}; maximum ${String(maximum)}). Raising ${budget} further is not supported. Select a smaller firmware byte range or analyze a smaller image. analysis_timeout_ms does not raise this staging allowance.`;
|
||||
};
|
||||
|
||||
/** The same selected aggregate budget is enforced during and after extraction. */
|
||||
const firmwareStagingLimit = (
|
||||
operation: string,
|
||||
@@ -455,7 +480,7 @@ const firmwareStagingLimit = (
|
||||
{ [field]: maximum },
|
||||
{
|
||||
retryable: false,
|
||||
remediationAction: `Increase ${field} within its schema maximum if the larger extraction is required, or select a materially narrower range. analysis_timeout_ms does not raise this staging allowance. Repeating the unchanged request fails the same way.`,
|
||||
remediationAction: firmwareStagingBudgetRemediation(field, maximum),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
MAX_FIRMWARE_STAGING_BYTES,
|
||||
MAX_FIRMWARE_STAGING_ENTRIES,
|
||||
} from "../domain/firmware/firmwareAnalysis.js";
|
||||
import { firmwareStagingBudgetRemediation } from "./FirmwareFiles.js";
|
||||
|
||||
describe("firmware staging budget recovery", () => {
|
||||
it("offers room to raise a budget that is below its supported maximum", () => {
|
||||
const remediation = firmwareStagingBudgetRemediation("max_output_files", 1);
|
||||
|
||||
expect(remediation).toContain(
|
||||
`supported maximum ${String(MAX_FIRMWARE_STAGING_ENTRIES)}`,
|
||||
);
|
||||
expect(remediation).toContain("Increase max_output_files up to");
|
||||
expect(remediation).not.toContain("not supported");
|
||||
});
|
||||
|
||||
it("does not recommend raising a budget already at its supported maximum", () => {
|
||||
const entries = firmwareStagingBudgetRemediation(
|
||||
"max_output_files",
|
||||
MAX_FIRMWARE_STAGING_ENTRIES,
|
||||
);
|
||||
const bytes = firmwareStagingBudgetRemediation(
|
||||
"max_output_bytes",
|
||||
MAX_FIRMWARE_STAGING_BYTES,
|
||||
);
|
||||
for (const remediation of [entries, bytes]) {
|
||||
// Raising a value the schema already caps is not an available recovery.
|
||||
expect(remediation).toContain("not supported");
|
||||
expect(remediation).not.toContain("Increase");
|
||||
expect(remediation).toContain("smaller firmware byte range");
|
||||
}
|
||||
});
|
||||
|
||||
it("names the exhausted budget, the selected value and the ceiling", () => {
|
||||
expect(firmwareStagingBudgetRemediation("max_output_bytes", 4)).toContain(
|
||||
"max_output_bytes=4",
|
||||
);
|
||||
expect(
|
||||
firmwareStagingBudgetRemediation(
|
||||
"max_output_bytes",
|
||||
MAX_FIRMWARE_STAGING_BYTES,
|
||||
),
|
||||
).toContain(`maximum ${String(MAX_FIRMWARE_STAGING_BYTES)}`);
|
||||
});
|
||||
|
||||
it("keeps the ceiling advice off the schema limit for both budgets", () => {
|
||||
for (const [budget, maximum] of [
|
||||
["max_output_files", MAX_FIRMWARE_STAGING_ENTRIES],
|
||||
["max_output_bytes", MAX_FIRMWARE_STAGING_BYTES],
|
||||
] as const)
|
||||
expect(firmwareStagingBudgetRemediation(budget, maximum - 1)).toContain(
|
||||
`Increase ${budget} up to`,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user