fix(audit): require integer rolling retention keep limits

Validate keepPerFamily/--keep-per-family as non-negative integers, remove silent flooring, add regression coverage, and sync runbook/docs wording.
This commit is contained in:
William Valentin
2026-02-27 13:11:31 -08:00
parent c68fd2498e
commit 06998ac65d
9 changed files with 46 additions and 12 deletions
+11 -5
View File
@@ -29,13 +29,19 @@ function isoDateTagNow(): string {
return new Date().toISOString().slice(0, 10);
}
function parseOptionalNumber(raw: string | undefined, flag: string): number | undefined {
function parseOptionalInteger(raw: string | undefined, flag: string): number | undefined {
if (!raw) {
return undefined;
}
const parsed = Number(raw);
if (!Number.isFinite(parsed)) {
throw new Error(`Invalid ${flag} value "${raw}". Expected a number.`);
throw new Error(`Invalid ${flag} value "${raw}". Expected an integer.`);
}
if (!Number.isInteger(parsed)) {
throw new Error(`Invalid ${flag} value "${raw}". Expected an integer.`);
}
if (parsed < 0) {
throw new Error(`${flag} must be greater than or equal to 0.`);
}
return parsed;
}
@@ -94,7 +100,7 @@ async function main(): Promise<void> {
}
const artifactsDir = resolve(values['artifacts-dir'] ?? 'docs/plans/artifacts');
const keepPerFamily = parseOptionalNumber(values['keep-per-family'], '--keep-per-family') ?? 8;
const keepPerFamily = parseOptionalInteger(values['keep-per-family'], '--keep-per-family') ?? 8;
const apply = Boolean(values.apply);
const format = values.format ?? 'text';
const reportTag = values['report-tag'] ?? isoDateTagNow();
@@ -128,7 +134,7 @@ async function main(): Promise<void> {
const payload = {
generated_at: new Date().toISOString(),
artifacts_dir: artifactsDir,
keep_per_family: Math.floor(keepPerFamily),
keep_per_family: keepPerFamily,
apply,
report_tag: reportTag,
reports: {
@@ -138,7 +144,7 @@ async function main(): Promise<void> {
plan,
};
const jsonOutput = JSON.stringify(payload, null, 2);
const textOutput = renderText(plan, artifactsDir, Math.floor(keepPerFamily), apply);
const textOutput = renderText(plan, artifactsDir, keepPerFamily, apply);
if (summaryJsonOut) {
await writeTextFile(summaryJsonOut, jsonOutput);