From ce84aa48d034257beea8174ec2e8c65e6bc7d28f Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 05:41:32 -0700 Subject: [PATCH] test(self-healing): cover the renamed-board starved-refinement wake that main's conversion lacked (#3116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Rebased onto current `main`, and it shrank to one test.** Was "self-healing consolidated (45 → 39)". ## What happened **Every code change in this PR landed independently from other workers** while it was open, and in each case theirs is equal or better. I took theirs and dropped mine: | My change | Landed on `main` as | |---|---| | pre-execution worktree seizure | `preExecLiveColumns` — same "dangerous direction" reasoning | | FN-5256 liveness cluster | `worktreeReconcileWipColumns` / `worktreeReconcileReviewColumns` | | agent-link membership | `agentLinkLiveColumns` / `agentLinkTerminalColumns` | | starved-refinement peer progress | `starvedWaitingColumns` — a project union covering both duplicated sites | Resolving the rebase by taking `main` left two orphaned declarations (`activeOrQueuedColumns`, `holdPeerIds`) that nothing referenced. `tsc` doesn't flag unused locals here, so I checked references by hand and removed them rather than ship dead code that reads as converted. ## What's worth landing **Their starved-refinement conversion has no renamed-board test — the suite had zero.** This adds one. A candidate resting in a renamed **intake** lane, with its peers in a renamed **hold** lane, must still escalate. The two are deliberately distinct columns so a wrong role set resolves no peers and escalates nothing; a fixture where they coincide would pass either way. The fake needed `listWorkflowDefinitions` — `starvedWaitingColumns` is a **project union**, so per-task selection readers alone leave it resolving nothing and the test would pass for the wrong reason. That mismatch is how I found the gap: my original test failed against their implementation. ## Verification - Green against **their** code - **Revert-proof against theirs:** restoring the literal fails it — 0 escalations against 1 expected - 8 tests in the suite green ## Note for the fleet This is the second PR of mine to shrink to a test on rebase (#3096 was the first). Both times the duplicated work was real and mine was the later arrival. The pattern is worth acting on at the coordination level, not by me working faster. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) --- .../self-healing-starved-refinement.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/engine/src/__tests__/self-healing-starved-refinement.test.ts b/packages/engine/src/__tests__/self-healing-starved-refinement.test.ts index 7a76108d9b..a1d2cf11c6 100644 --- a/packages/engine/src/__tests__/self-healing-starved-refinement.test.ts +++ b/packages/engine/src/__tests__/self-healing-starved-refinement.test.ts @@ -77,6 +77,64 @@ describe("SelfHealingManager.recoverStarvedRefinementTriageTasks", () => { vi.useRealTimers(); }); + /* + FNXC:WorkflowResolvedColumns 2026-07-31-16:40 (fleet — peer-progress vocabulary): + "Peer progress" was `peer.column === "todo"` in two places: the candidate filter and the count written + into the log line and audit metadata. On a renamed board both stopped matching, so peer progress read + as zero and starved refinements were never escalated — silently, since a zero count is indistinguishable + from a genuinely quiet board. + + The board below separates intake from hold on purpose. The candidate rests in INTAKE (`inbox`) and its + peers in HOLD (`backlog`), so a conversion that reached for the wrong role set — or that left either + site on the literal — resolves no peers and escalates nothing. + */ + it("counts peer progress in the board's own HOLD lane on a renamed board", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-05-15T11:00:00.000Z")); + + const RENAMED_IR = { + version: "v2", + name: "renamed-starvation", + columns: [ + { id: "inbox", name: "Inbox", traits: [{ trait: "intake" }] }, + { id: "backlog", name: "Backlog", traits: [{ trait: "hold" }] }, + { id: "building", name: "Building", traits: [{ trait: "wip" }] }, + ], + nodes: [], + edges: [], + }; + + const tasks: Task[] = [ + task({ id: "FN-R9", column: "inbox", sourceType: "task_refine", createdAt: "2026-05-15T10:00:00.000Z", updatedAt: "2026-05-15T10:00:00.000Z", priority: "low" }), + task({ id: "FN-Q1", column: "backlog", sourceType: "dashboard_ui", updatedAt: "2026-05-15T10:15:00.000Z" }), + task({ id: "FN-Q2", column: "backlog", sourceType: "dashboard_ui", updatedAt: "2026-05-15T10:16:00.000Z" }), + task({ id: "FN-Q3", column: "backlog", sourceType: "dashboard_ui", updatedAt: "2026-05-15T10:17:00.000Z" }), + ]; + + const updateTask = vi.fn(async () => undefined); + const store: any = { + getSettings: vi.fn().mockResolvedValue({ globalPause: false, enginePaused: false }), + listTasks: vi.fn().mockResolvedValue(tasks), + updateTask, + logEntry: vi.fn().mockResolvedValue(undefined), + recordRunAuditEvent: vi.fn().mockResolvedValue(undefined), + getTaskWorkflowSelection: vi.fn(() => ({ workflowId: "custom:renamed", stepIds: [] })), + getTaskWorkflowSelectionAsync: vi.fn(async () => ({ workflowId: "custom:renamed", stepIds: [] })), + getWorkflowDefinition: vi.fn(async () => ({ ir: RENAMED_IR })), + /* `starvedWaitingColumns` is a PROJECT union (`resolveProjectColumnsForRoles`), so the fake needs + `listWorkflowDefinitions`; the per-task selection readers alone leave it resolving nothing and + the test would pass for the wrong reason. */ + listWorkflowDefinitions: vi.fn(async () => [{ id: "custom:renamed", ir: RENAMED_IR }]), + on: () => {}, + removeListener: () => {}, + }; + + const manager = new SelfHealingManager(store, { rootDir: process.cwd(), getPlanningTaskIds: () => new Set() }); + await expect(manager.recoverStarvedRefinementTriageTasks()).resolves.toBe(1); + expect(updateTask).toHaveBeenCalledWith("FN-R9", { priority: "normal" }); + vi.useRealTimers(); + }); + it("does not escalate non-refinement triage tasks", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-05-15T11:00:00.000Z"));