import assert from "node:assert/strict"; import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { test } from "node:test"; import { DEFAULT_MAX_NO_PROGRESS_REPORTS, initialState, recordCheckInjected, recordInProgressReport, recordWorkEvent, setGoal, type ProgressDetails, } from "../lib/state.ts"; const measurement: ProgressDetails = { kind: "measurement", gate: "PR review", owner: "rev-code-01", lastMeasurement: "PR head abc123 remains unreviewed at 2026-08-31T15:00:00Z", nextAction: "dispatch exact-head review", }; function reportOnlyCycle(state: ReturnType) { const checked = recordCheckInjected(state); return recordInProgressReport(checked, { evidence: "", progress: undefined }).state; } test("exact report-only loop pauses within the no-progress bound", () => { let state = setGoal(initialState(), "ship", 25); for (let n = 1; n <= DEFAULT_MAX_NO_PROGRESS_REPORTS; n++) { state = reportOnlyCycle(state); assert.equal(state.noProgressReports, n); } assert.equal(state.status, "paused"); assert.match(state.pausedReason ?? "", /no substantive progress/); }); test("empty, malformed, and duplicate evidence never reset no-progress", () => { let state = setGoal(initialState(), "ship", 25); state = recordInProgressReport(recordCheckInjected(state), { evidence: "", progress: undefined }).state; assert.equal(state.noProgressReports, 1); state = recordInProgressReport(recordCheckInjected(state), { evidence: "measured", progress: { ...measurement, owner: "" }, }).state; assert.equal(state.noProgressReports, 2); state = recordWorkEvent(state, "read", "2026-08-31T15:00:00Z"); const accepted = recordInProgressReport(recordCheckInjected(state), { evidence: "measured live PR state", progress: measurement, }); assert.equal(accepted.classification, "progress"); state = accepted.state; assert.equal(state.noProgressReports, 0); state = recordWorkEvent(state, "read", "2026-08-31T15:01:00Z"); const duplicate = recordInProgressReport(recordCheckInjected(state), { evidence: "measured live PR state", progress: measurement, }); assert.equal(duplicate.classification, "no_progress"); assert.equal(duplicate.state.noProgressReports, 1); }); test("measurement, action, and delegation require an observed successful work event", () => { for (const kind of ["measurement", "action", "delegation"] as const) { const details = { ...measurement, kind }; let state = setGoal(initialState(), kind, 25); const unsupported = recordInProgressReport(recordCheckInjected(state), { evidence: `${kind} claim`, progress: details, }); assert.equal(unsupported.classification, "no_progress"); state = recordWorkEvent(unsupported.state, kind === "action" ? "edit" : "bash", "2026-08-31T15:00:00Z"); const accepted = recordInProgressReport(recordCheckInjected(state), { evidence: `${kind} completed with a new result`, progress: { ...details, lastMeasurement: `${details.lastMeasurement} ${kind}` }, }); assert.equal(accepted.classification, "progress"); assert.equal(accepted.state.noProgressReports, 0); assert.equal(accepted.state.checks, 0); } }); test("explicit non-tool work requires a concrete artifact", () => { let state = setGoal(initialState(), "draft", 25); const missing = recordInProgressReport(recordCheckInjected(state), { evidence: "drafted reasoning", progress: { ...measurement, kind: "non_tool" }, }); assert.equal(missing.classification, "no_progress"); const accepted = recordInProgressReport(recordCheckInjected(missing.state), { evidence: "completed architecture decision", progress: { ...measurement, kind: "non_tool", artifact: "work/decision-D12.md" }, }); assert.equal(accepted.classification, "progress"); assert.equal(accepted.state.noProgressReports, 0); }); test("a legitimate external wait remains active without false-positive pause", () => { let state = setGoal(initialState(), "await review", 25); state = recordInProgressReport(recordCheckInjected(state), { evidence: "", progress: undefined }).state; state = recordInProgressReport(recordCheckInjected(state), { evidence: "", progress: undefined }).state; assert.equal(state.noProgressReports, 2); const waiting: ProgressDetails = { kind: "wait", gate: "independent review", owner: "rev-code-01", lastMeasurement: "review dispatched at 2026-08-31T15:00:00Z", nextAction: "merge only after PASS", watchId: "ng8-review", nextCheck: "review artifact exists", }; for (let n = 0; n < 10; n++) { const outcome = recordInProgressReport(recordCheckInjected(state), { evidence: "approved watch remains active", progress: waiting, }); assert.equal(outcome.classification, "waiting"); state = outcome.state; assert.equal(state.status, "active"); assert.equal(state.noProgressReports, 2); assert.equal(state.checks, 0); } }); test("RED CONTROL: neutralizing no-progress counting restores the infinite loop", async () => { const source = readFileSync(new URL("../lib/state.ts", import.meta.url), "utf8"); const sabotaged = source.replace( "const nextNoProgress = state.noProgressReports + 1; // NG8_COUNT_GATE", "const nextNoProgress = state.noProgressReports; // NG8_COUNT_GATE neutralized", ); assert.notEqual(sabotaged, source, "count sabotage must change source"); const dir = mkdtempSync(join(tmpdir(), "goal-ng8-count-red-")); try { const path = join(dir, "state.ts"); writeFileSync(path, sabotaged); const red = await import(`${pathToFileURL(path).href}?red=${Date.now()}`); let state = red.setGoal(red.initialState(), "ship", 25); for (let n = 0; n < DEFAULT_MAX_NO_PROGRESS_REPORTS + 2; n++) { state = red.recordCheckInjected(state); state = red.recordInProgressReport(state, { evidence: "", progress: undefined }).state; } assert.equal(state.status, "active", "RED: report-only loop survives when counting is neutralized"); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("RED CONTROL: neutralizing pause enforcement leaves the counted loop active", async () => { const source = readFileSync(new URL("../lib/state.ts", import.meta.url), "utf8"); const sabotaged = source.replace( "if (nextNoProgress >= state.maxNoProgressReports) { // NG8_PAUSE_GATE", "if (false && nextNoProgress >= state.maxNoProgressReports) { // NG8_PAUSE_GATE neutralized", ); assert.notEqual(sabotaged, source, "pause sabotage must change source"); const dir = mkdtempSync(join(tmpdir(), "goal-ng8-pause-red-")); try { const path = join(dir, "state.ts"); writeFileSync(path, sabotaged); const red = await import(`${pathToFileURL(path).href}?red=${Date.now()}`); let state = red.setGoal(red.initialState(), "ship", 25); for (let n = 0; n < DEFAULT_MAX_NO_PROGRESS_REPORTS; n++) { state = red.recordCheckInjected(state); state = red.recordInProgressReport(state, { evidence: "", progress: undefined }).state; } assert.equal(state.noProgressReports, DEFAULT_MAX_NO_PROGRESS_REPORTS); assert.equal(state.status, "active", "RED: counted loop survives when pause gate is neutralized"); } finally { rmSync(dir, { recursive: true, force: true }); } });