feat(FN-5000): merge fusion/fn-5000

This commit is contained in:
gsxdsm
2026-05-17 22:11:42 -07:00
parent b143f6c88b
commit 3cae2a30b9

View File

@@ -10,7 +10,12 @@ vi.mock("node:fs", () => ({
import { PoolDoubleLeaseError, WorktreePool } from "../worktree-pool.js"; import { PoolDoubleLeaseError, WorktreePool } from "../worktree-pool.js";
// FN-4954 deterministic race backstop. // FN-5000:
// - A prior "delay prepareForTask" sketch was invalid because acquire() is synchronous,
// so both racers complete acquire before any async barrier can interleave execution.
// - The release-during-peer-acquire case documents the incident-shaped bug surface.
// - The two-acquire case locks the surviving invariant: one non-null result, one null.
// - The rehydrate case covers rehydrate collision behavior.
describe("WorktreePool double-lease guard", () => { describe("WorktreePool double-lease guard", () => {
let pool: WorktreePool; let pool: WorktreePool;
@@ -54,6 +59,41 @@ describe("WorktreePool double-lease guard", () => {
expect(violations).toEqual([{ phase: "acquire", requestingTaskId: "FN-B", existingHolder: "FN-A" }]); expect(violations).toEqual([{ phase: "acquire", requestingTaskId: "FN-B", existingHolder: "FN-A" }]);
}); });
it("FN-4954: release during peer acquire hands out the same path twice", () => {
const violations: Array<{ phase: string; requestingTaskId: string; existingHolder: string }> = [];
pool.setInvariantViolationHandler((violation) => {
violations.push({
phase: violation.phase,
requestingTaskId: violation.requestingTaskId,
existingHolder: violation.existingHolder,
});
});
pool.release("/tmp/wt-A");
const pathA = pool.acquire("FN-A");
expect(pathA).toBe("/tmp/wt-A");
expect(pool.size).toBe(0);
// FN-4928/FN-4939 shape: stale/error cleanup releases with a mismatched task id.
pool.release(pathA!, "FN-B");
const pathB = pool.acquire("FN-B");
expect(pathB).toBe("/tmp/wt-A");
expect(violations).toEqual([{ phase: "release", requestingTaskId: "FN-B", existingHolder: "FN-A" }]);
});
it("FN-4954: two synchronous acquire() calls against a single-item pool yield exactly one non-null", () => {
pool.release("/tmp/wt-A");
const results = [pool.acquire("FN-A"), pool.acquire("FN-B")];
const nonNull = results.filter((r): r is string => r !== null);
expect(nonNull).toEqual(["/tmp/wt-A"]);
expect(results.filter((r) => r === null)).toHaveLength(1);
expect(pool.size).toBe(0);
});
it("does not throw for same-task re-entry when no idle path exists", () => { it("does not throw for same-task re-entry when no idle path exists", () => {
pool.release("/tmp/wt-race"); pool.release("/tmp/wt-race");
expect(pool.acquire("FN-A")).toBe("/tmp/wt-race"); expect(pool.acquire("FN-A")).toBe("/tmp/wt-race");