From 9f56364584fee1a36f605e3e885869d0ca3ef42e Mon Sep 17 00:00:00 2001 From: Panopticon Date: Tue, 21 Jul 2026 23:49:34 +0000 Subject: [PATCH 1/3] spec: define audit review exit semantics --- .2119/verdicts/REQ-009.1.1--425359da4a3d.json | 8 +++++ .2119/verdicts/REQ-009.1.2--6fd6ac75a9da.json | 8 +++++ specs/REQ-009-audit-exit-status.md | 19 ++++++++++++ tests/rigor.test.ts | 29 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 .2119/verdicts/REQ-009.1.1--425359da4a3d.json create mode 100644 .2119/verdicts/REQ-009.1.2--6fd6ac75a9da.json create mode 100644 specs/REQ-009-audit-exit-status.md diff --git a/.2119/verdicts/REQ-009.1.1--425359da4a3d.json b/.2119/verdicts/REQ-009.1.1--425359da4a3d.json new file mode 100644 index 0000000..26eec61 --- /dev/null +++ b/.2119/verdicts/REQ-009.1.1--425359da4a3d.json @@ -0,0 +1,8 @@ +{ + "reviewId": "REQ-009.1.1--425359da4a3d", + "requirementId": "REQ-009.1.1", + "hash": "425359da4a3d", + "verdict": "pass", + "summary": "Real-CLI test creates an audit with its sole judgment already passed and directly asserts generated evidence plus exit 0.", + "timestamp": "2026-07-21T23:39:48.163Z" +} diff --git a/.2119/verdicts/REQ-009.1.2--6fd6ac75a9da.json b/.2119/verdicts/REQ-009.1.2--6fd6ac75a9da.json new file mode 100644 index 0000000..e906ba3 --- /dev/null +++ b/.2119/verdicts/REQ-009.1.2--6fd6ac75a9da.json @@ -0,0 +1,8 @@ +{ + "reviewId": "REQ-009.1.2--6fd6ac75a9da", + "requirementId": "REQ-009.1.2", + "hash": "6fd6ac75a9da", + "verdict": "pass", + "summary": "Real-CLI test creates a passed audit target alongside one pending judgment and directly asserts audit generation plus exit 1.", + "timestamp": "2026-07-21T23:39:48.162Z" +} diff --git a/specs/REQ-009-audit-exit-status.md b/specs/REQ-009-audit-exit-status.md new file mode 100644 index 0000000..1a42117 --- /dev/null +++ b/specs/REQ-009-audit-exit-status.md @@ -0,0 +1,19 @@ +# REQ-009: Audit Exit Status + +## Overview + +`2119 review --audit` is a discretionary quality-assurance command: it writes adversarial +instructions for requirements that already have passing verdicts so an operator can challenge +those verdicts with a fresh reviewer. Today, a successful audit-only run exits 1 even though no +required judgment review is pending. That makes a healthy periodic audit sweep look like a gate +failure to shell scripts and CI, and makes the `audit: always` configuration costly to automate. + +Audit generation should report whether the command succeeded; the existing non-zero signal for +required, pending judgment reviews remains unchanged. + +## Requirements + +### REQ-009.1: Optional audit success + +1. When `2119 review` generates one or more adversarial audit instruction files and no judgment review is pending, the command MUST exit 0. +2. When any judgment review is pending, `2119 review` MUST retain its non-zero exit status even if it also generates adversarial audit instruction files. diff --git a/tests/rigor.test.ts b/tests/rigor.test.ts index 812758d..13df31c 100644 --- a/tests/rigor.test.ts +++ b/tests/rigor.test.ts @@ -178,6 +178,35 @@ describe("deterministic rigor (0.6)", () => { expect(existsSync(join(root, `.2119/reviews/${id}.audit.md`))).toBe(true); }); + // 2119: REQ-009.1.1 + it("exits zero when an audit-only review run generates instructions", () => { + const root = fixture(); + const id = run(root, ["review"]).stdout.match(/FIX-001\.1\.1--[0-9a-f]{12}/)![0]; + expect(run(root, ["pass", id, "--summary", "asserts spin behavior"]).status).toBe(0); + + const result = run(root, ["review", "--audit"]); + expect(existsSync(join(root, `.2119/reviews/${id}.audit.md`))).toBe(true); + expect(result.stdout).toContain("adversarial audit(s) generated"); + expect(result.status).toBe(0); + }); + + // 2119: REQ-009.1.2 + it("retains a non-zero exit when audits and pending reviews coexist", () => { + const root = fixture(`${SPEC}2. The widget MUST stop.\n`); + writeFileSync( + join(root, "tests/widget.test.js"), + "// 2119: FIX-001.1.1\ntest('spins', () => {})\n// 2119: FIX-001.1.2\ntest('stops', () => {})\n", + ); + const initial = run(root, ["review"]).stdout; + const passingId = initial.match(/FIX-001\.1\.1--[0-9a-f]{12}/)![0]; + expect(run(root, ["pass", passingId, "--summary", "asserts spin behavior"]).status).toBe(0); + + const result = run(root, ["review", "--audit"]); + expect(existsSync(join(root, `.2119/reviews/${passingId}.audit.md`))).toBe(true); + expect(result.stdout).toContain("judgment review(s) pending"); + expect(result.status).toBe(1); + }); + // 2119: REQ-004.1.10 it("session-start injects an upgrade notice from a cached probe", async () => { const root = fixture(); From d893dc69aa24b588c333955729e02f7d7e6a3194 Mon Sep 17 00:00:00 2001 From: Panopticon Date: Tue, 21 Jul 2026 23:49:34 +0000 Subject: [PATCH 2/3] fix: let audit-only review exit successfully --- src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index 8c33e61..12dc61f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -126,7 +126,7 @@ switch (command) { } if (tasks.length === 0) { console.log("review: no pending reviews; dispatch the audits above to fresh-context reviewers."); - process.exit(1); + break; } console.log( `${tasks.length} judgment review(s) pending. Dispatch each to a FRESH-CONTEXT reviewer\n` + From 4daf9e7abc39fd34bc1865369ed1cc1aa329d756 Mon Sep 17 00:00:00 2001 From: Panopticon Date: Tue, 21 Jul 2026 23:54:00 +0000 Subject: [PATCH 3/3] chore: refresh source review verdict --- .2119/verdicts/REQ-003.2.2--0e2870584d77.json | 8 -------- .2119/verdicts/REQ-003.2.2--204eb12e5588.json | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 .2119/verdicts/REQ-003.2.2--0e2870584d77.json create mode 100644 .2119/verdicts/REQ-003.2.2--204eb12e5588.json diff --git a/.2119/verdicts/REQ-003.2.2--0e2870584d77.json b/.2119/verdicts/REQ-003.2.2--0e2870584d77.json deleted file mode 100644 index d571eed..0000000 --- a/.2119/verdicts/REQ-003.2.2--0e2870584d77.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "reviewId": "REQ-003.2.2--0e2870584d77", - "requirementId": "REQ-003.2.2", - "hash": "0e2870584d77", - "verdict": "pass", - "summary": "writeVerdict emits plain JSON to .2119/verdicts/; init gitignores only .2119/reviews/, leaving verdicts committed (verified: verdict .json files are git-tracked, dir absent from .gitignore)", - "timestamp": "2026-07-13T16:01:22.723Z" -} diff --git a/.2119/verdicts/REQ-003.2.2--204eb12e5588.json b/.2119/verdicts/REQ-003.2.2--204eb12e5588.json new file mode 100644 index 0000000..0d30d60 --- /dev/null +++ b/.2119/verdicts/REQ-003.2.2--204eb12e5588.json @@ -0,0 +1,8 @@ +{ + "reviewId": "REQ-003.2.2--204eb12e5588", + "requirementId": "REQ-003.2.2", + "hash": "204eb12e5588", + "verdict": "pass", + "summary": "writeVerdict emits readable JSON under .2119/verdicts; init ignores only .2119/reviews, and Git confirms verdict JSON is tracked and not ignored for PR audit", + "timestamp": "2026-07-21T23:53:11.020Z" +}