diff --git a/src/resources/extensions/gsd/state.ts b/src/resources/extensions/gsd/state.ts index 2d84782c5..e6ff91895 100644 --- a/src/resources/extensions/gsd/state.ts +++ b/src/resources/extensions/gsd/state.ts @@ -90,18 +90,13 @@ export function isMilestoneComplete(roadmap: Roadmap): boolean { } /** - * Check whether a VALIDATION file's verdict is terminal (pass or needs-attention). - * A non-terminal verdict (needs-remediation) means validation must re-run - * after remediation slices are executed. + * Check whether a VALIDATION file's verdict is terminal. + * Any successfully extracted verdict (pass, needs-attention, needs-remediation, + * fail, etc.) means validation completed. Only return false when no verdict + * could be parsed — i.e. extractVerdict() returns undefined (#2769). */ export function isValidationTerminal(validationContent: string): boolean { - const v = extractVerdict(validationContent); - if (!v) return false; - // 'pass' and 'needs-attention' are always terminal. - // 'needs-remediation' is treated as terminal to prevent infinite loops - // when no remediation slices exist in the roadmap (#832). The validation - // report is preserved on disk for manual review. - return v === 'pass' || v === 'needs-attention' || v === 'needs-remediation'; + return extractVerdict(validationContent) != null; } // ─── State Derivation ────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/validate-milestone.test.ts b/src/resources/extensions/gsd/tests/validate-milestone.test.ts index 79fe1c35f..9647ddb46 100644 --- a/src/resources/extensions/gsd/tests/validate-milestone.test.ts +++ b/src/resources/extensions/gsd/tests/validate-milestone.test.ts @@ -110,6 +110,16 @@ test("isValidationTerminal returns true for verdict: passed (#1429)", () => { assert.equal(isValidationTerminal(content), true); }); +test("isValidationTerminal returns true for verdict: fail (#2769)", () => { + const content = "---\nverdict: fail\nremediation_round: 1\n---\n\n# Validation"; + assert.equal(isValidationTerminal(content), true); +}); + +test("isValidationTerminal returns true for any arbitrary verdict string (#2769)", () => { + const content = "---\nverdict: custom-verdict\nremediation_round: 0\n---\n\n# Validation"; + assert.equal(isValidationTerminal(content), true); +}); + test("isValidationTerminal returns false for missing frontmatter", () => { const content = "# Validation\nNo frontmatter here."; assert.equal(isValidationTerminal(content), false); @@ -327,14 +337,14 @@ test("verifyExpectedArtifact rejects VALIDATION with missing verdict field", () } }); -test("verifyExpectedArtifact rejects VALIDATION with unrecognized verdict", () => { +test("verifyExpectedArtifact accepts VALIDATION with any extracted verdict", () => { const base = makeTmpBase(); try { writeValidation(base, "M001", "---\nverdict: unknown-value\nremediation_round: 0\n---\n\n# Validation"); clearPathCache(); clearParseCache(); const result = verifyExpectedArtifact("validate-milestone", "M001", base); - assert.equal(result, false, "VALIDATION with unrecognized verdict should fail verification"); + assert.equal(result, true, "VALIDATION with any extracted verdict should pass verification"); } finally { cleanup(base); }