diff --git a/packages/loopover-engine/src/review/screenshot-table-gate.ts b/packages/loopover-engine/src/review/screenshot-table-gate.ts index 15e3d756b..bc741505e 100644 --- a/packages/loopover-engine/src/review/screenshot-table-gate.ts +++ b/packages/loopover-engine/src/review/screenshot-table-gate.ts @@ -77,7 +77,12 @@ export function normalizeScreenshotTableGateConfig(input: unknown, warnings: str const action = isScreenshotTableGateAction(record.action) ? record.action : (() => { - if (record.action !== undefined) warnings.push(`settings.requireScreenshotTable.action must be "close" or "advisory" (#4110 removed request_changes/comment as dead config surface); using the default "close".`); + // Derived from VALID_ACTIONS (mirrors normalizeSelfLoopAutonomy in ams-policy-spec.ts) so a future + // tier addition can't desynchronize this message again. + if (record.action !== undefined) + warnings.push( + `settings.requireScreenshotTable.action must be one of ${VALID_ACTIONS.join(", ")} (#4110 removed request_changes/comment as dead config surface); using the default "${DEFAULT_SCREENSHOT_TABLE_GATE.action}".`, + ); return DEFAULT_SCREENSHOT_TABLE_GATE.action; })(); const message = typeof record.message === "string" && record.message.trim().length > 0 ? record.message.trim() : undefined; diff --git a/packages/loopover-engine/test/screenshot-table-gate.test.ts b/packages/loopover-engine/test/screenshot-table-gate.test.ts new file mode 100644 index 000000000..d48044537 --- /dev/null +++ b/packages/loopover-engine/test/screenshot-table-gate.test.ts @@ -0,0 +1,29 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { normalizeScreenshotTableGateConfig } from "../dist/review/screenshot-table-gate.js"; + +// #9996: the invalid-action warning was left naming only "close" or "advisory" after #9964 added the +// non-destructive "block" tier -- an operator who mistyped the value was never told "block" exists. + +test("normalizeScreenshotTableGateConfig warns and mentions block for an invalid action", () => { + const warnings: string[] = []; + const config = normalizeScreenshotTableGateConfig({ enabled: true, action: "blok" }, warnings); + assert.equal(warnings.length, 1); + assert.match(warnings[0] ?? "", /block/); + assert.equal(config.action, "close"); +}); + +test("normalizeScreenshotTableGateConfig pushes no action warning when action is absent", () => { + const warnings: string[] = []; + const config = normalizeScreenshotTableGateConfig({ enabled: true }, warnings); + assert.equal(warnings.length, 0); + assert.equal(config.action, "close"); +}); + +test("normalizeScreenshotTableGateConfig resolves a valid block action with no warning", () => { + const warnings: string[] = []; + const config = normalizeScreenshotTableGateConfig({ enabled: true, action: "block" }, warnings); + assert.equal(warnings.length, 0); + assert.equal(config.action, "block"); +}); diff --git a/test/unit/screenshot-table-gate-engine.test.ts b/test/unit/screenshot-table-gate-engine.test.ts index eb99f16b3..df1cdd3bb 100644 --- a/test/unit/screenshot-table-gate-engine.test.ts +++ b/test/unit/screenshot-table-gate-engine.test.ts @@ -235,6 +235,27 @@ describe("normalizeScreenshotTableGateConfig", () => { expect(warnings.some((w) => w.includes("action"))).toBe(true); }); + it("#9996: the invalid-action warning names all three valid tiers, including block", () => { + const warnings: string[] = []; + const result = normalizeScreenshotTableGateConfig({ enabled: true, action: "blok" }, warnings); + expect(warnings.length).toBe(1); + expect(warnings[0]).toContain("block"); + expect(result.action).toBe("close"); + }); + + it("#9996: an absent action still pushes no warning", () => { + const warnings: string[] = []; + normalizeScreenshotTableGateConfig({ enabled: true }, warnings); + expect(warnings.length).toBe(0); + }); + + it("#9996: a valid block action resolves with no warning", () => { + const warnings: string[] = []; + const result = normalizeScreenshotTableGateConfig({ enabled: true, action: "block" }, warnings); + expect(result.action).toBe("block"); + expect(warnings.length).toBe(0); + }); + it("rejects a non-string/empty message with a warning, falling back to undefined", () => { const warnings: string[] = []; const result = normalizeScreenshotTableGateConfig({ message: " " }, warnings); diff --git a/test/unit/screenshot-table-gate.test.ts b/test/unit/screenshot-table-gate.test.ts index 5e77f324d..47ad62e5e 100644 --- a/test/unit/screenshot-table-gate.test.ts +++ b/test/unit/screenshot-table-gate.test.ts @@ -219,6 +219,27 @@ describe("normalizeScreenshotTableGateConfig", () => { expect(warnings.some((w) => w.includes("action"))).toBe(true); }); + it("#9996: the invalid-action warning names all three valid tiers, including block", () => { + const warnings: string[] = []; + const result = normalizeScreenshotTableGateConfig({ enabled: true, action: "blok" }, warnings); + expect(warnings.length).toBe(1); + expect(warnings[0]).toContain("block"); + expect(result.action).toBe("close"); + }); + + it("#9996: an absent action still pushes no warning", () => { + const warnings: string[] = []; + normalizeScreenshotTableGateConfig({ enabled: true }, warnings); + expect(warnings.length).toBe(0); + }); + + it("#9996: a valid block action resolves with no warning", () => { + const warnings: string[] = []; + const result = normalizeScreenshotTableGateConfig({ enabled: true, action: "block" }, warnings); + expect(result.action).toBe("block"); + expect(warnings.length).toBe(0); + }); + it("rejects a non-string/empty message with a warning, falling back to undefined", () => { const warnings: string[] = []; const result = normalizeScreenshotTableGateConfig({ message: " " }, warnings);