test(engine): pin both dependency-lease resolvers on a renamed board (one case covered only half the conversion) (#3138)
Next two entries from the verified coverage map on #3115. `reconcileDependencyBlockingLeases` had **both** of its resolvers uncovered — blinding either `leaseWipColumns` or `leaseHoldColumns` back to its legacy id left all 825 self-healing tests green, because every fixture in that block uses `in-progress` / `todo`, where the literals happen to be correct. ## What the literals cost The holder scan matches no card **and** the dependency scan matches no card. A stale file-scope lease blocking a real dependency is never rebounded, so the dependent stays `overlapBlockedBy` behind a holder that is not coming back. That is the deadlock this sweep exists to break — silently not broken, no error, no log. ## Two cases, because one did not cover both — measured, not assumed My first case (holder in a renamed wip lane, dependency marked `overlapBlockedBy`) pinned `leaseWipColumns`. I then blinded `leaseHoldColumns` against it and **it stayed green**. The reason is in the control flow: the `overlapBlockedBy === holder.id` branch short-circuits and `break`s **before** the hold membership is consulted. So that fixture can never reach the guard `leaseHoldColumns` feeds. The second case drops the marker, leaving an unmarked dependency resting in a renamed hold lane, which falls through to the overlapping-hold-dependency branch. | blinded | result | |---|---| | `leaseWipColumns` | **1 failed** | | `leaseHoldColumns` | **1 failed** | Before the second case, that table read `1 failed` / `still green`. Checking each resolver separately is the only reason I noticed — a single "the suite fails when reverted" would have looked like proof and covered half the conversion. ## Remaining 23 uncovered resolvers on the map. Next by risk: `reclaimStaleActiveBranches` (deletes branches) and `reconcileInReviewBranchRebind` (rebinds branches of live cards), both needing a git-shelling harness. ## Verification `self-healing.test.ts` **415 passed** · `pnpm test:gate` 13 + 161 + 487 + 71 · lint — green. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved recovery of stalled workflow tasks when dependency-blocking leases become stale. * Added support for workflows using customized task status lanes, including marked and unmarked overlap blockers. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -11796,6 +11796,79 @@ describe("FN-5335 triple-proof no-action unit coverage", () => {
|
||||
manager.stop();
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-31-22:15:
|
||||
Both of this sweep's resolvers were UNCOVERED on the #3115 coverage map: blinding either
|
||||
`leaseWipColumns` or `leaseHoldColumns` back to its legacy id leaves all 825 self-healing tests
|
||||
green, because every fixture above uses `in-progress` / `todo`, where the literals are correct.
|
||||
|
||||
What that costs on a renamed board: the holder scan matches no card and the dependency scan
|
||||
matches no card, so a stale file-scope lease blocking a real dependency is never rebounded. The
|
||||
dependent stays `overlapBlockedBy` forever behind a holder that is not coming back — a deadlock
|
||||
the sweep exists to break, silently not broken.
|
||||
*/
|
||||
it("rebounds a stale lease when holder and dependency rest in RENAMED wip and hold lanes", async () => {
|
||||
const { store, manager } = setup([
|
||||
makeTask({ id: "FN-H", column: "building", dependencies: ["FN-D"], worktree: "/tmp/wt-h" }),
|
||||
makeTask({ id: "FN-D", column: "drafting", status: "queued", overlapBlockedBy: "FN-H" }),
|
||||
], {
|
||||
"FN-H": ["packages/engine/src/scheduler.ts"],
|
||||
"FN-D": ["packages/engine/src/scheduler.ts"],
|
||||
});
|
||||
(store as unknown as { listWorkflowDefinitions: unknown }).listWorkflowDefinitions = vi.fn(async () => [{
|
||||
ir: {
|
||||
version: "v2",
|
||||
id: "custom:renamed",
|
||||
nodes: [],
|
||||
edges: [],
|
||||
columns: [
|
||||
{ id: "drafting", name: "drafting", traits: [{ trait: "hold", config: { release: "capacity" } }] },
|
||||
{ id: "building", name: "building", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] },
|
||||
],
|
||||
},
|
||||
}]);
|
||||
vi.spyOn(manager as any, "evaluateBackwardMoveTripleProof").mockResolvedValue({ ok: true, stalenessMs: 10_000, reason: "test" });
|
||||
|
||||
await expect(manager.reconcileDependencyBlockingLeases()).resolves.toBe(1);
|
||||
/* The dependent is released rather than left blocked behind a holder that is not coming back. */
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-D", { overlapBlockedBy: null, status: null });
|
||||
manager.stop();
|
||||
});
|
||||
|
||||
it("rebounds an overlapping RENAMED-hold dependency with no stale-blocker marker", async () => {
|
||||
/*
|
||||
The hold half. The case above sets `overlapBlockedBy`, which short-circuits at the
|
||||
stale-overlap-blocker branch BEFORE the hold membership is consulted — measured: blinding
|
||||
`leaseHoldColumns` leaves it green. Without the marker the sweep must fall through to
|
||||
"is this dependency waiting in a hold lane and overlapping my scope?", which is the guard
|
||||
`leaseHoldColumns` actually feeds.
|
||||
*/
|
||||
const { store, manager } = setup([
|
||||
makeTask({ id: "FN-H2", column: "building", dependencies: ["FN-D2"], worktree: "/tmp/wt-h2" }),
|
||||
makeTask({ id: "FN-D2", column: "drafting", status: "queued" }),
|
||||
], {
|
||||
"FN-H2": ["packages/engine/src/scheduler.ts"],
|
||||
"FN-D2": ["packages/engine/src/scheduler.ts"],
|
||||
});
|
||||
(store as unknown as { listWorkflowDefinitions: unknown }).listWorkflowDefinitions = vi.fn(async () => [{
|
||||
ir: {
|
||||
version: "v2",
|
||||
id: "custom:renamed",
|
||||
nodes: [],
|
||||
edges: [],
|
||||
columns: [
|
||||
{ id: "drafting", name: "drafting", traits: [{ trait: "hold", config: { release: "capacity" } }] },
|
||||
{ id: "building", name: "building", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] },
|
||||
],
|
||||
},
|
||||
}]);
|
||||
vi.spyOn(manager as any, "evaluateBackwardMoveTripleProof").mockResolvedValue({ ok: true, stalenessMs: 10_000, reason: "test" });
|
||||
|
||||
await expect(manager.reconcileDependencyBlockingLeases()).resolves.toBe(1);
|
||||
expect(store.moveTask).toHaveBeenCalledWith("FN-H2", expect.anything(), expect.objectContaining({ recoveryRehome: true }));
|
||||
manager.stop();
|
||||
});
|
||||
|
||||
it("rebounds an overlapping todo dependency even before a stale blocker marker is stamped", async () => {
|
||||
const { store, manager } = setup([
|
||||
makeTask({ id: "FN-H", column: "in-progress", dependencies: ["FN-D"] }),
|
||||
|
||||
Reference in New Issue
Block a user