feat(FN-4935): complete Step 1 — structured liveness classifier

Ref: Runfusion/Fusion#601
Fusion-Task-Id: FN-4935
Fusion-Task-Lineage: 8c842b69-1427-47be-9ba5-8ec66449cc7c
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 14:22:09 -07:00
committed by gsxdsm
parent eba4eb5161
commit 0f69412c20
3 changed files with 107 additions and 13 deletions

View File

@@ -111,6 +111,7 @@ vi.mock("../worktree-pool.js", async (importOriginal) => {
return {
...actual,
classifyTaskWorktree: vi.fn().mockResolvedValue({ ok: true }),
describeRegisteredWorktrees: vi.fn().mockResolvedValue({ rawOutput: "", canonicalized: [] }),
isUsableTaskWorktree: vi.fn().mockResolvedValue(true),
};
});
@@ -247,7 +248,7 @@ import { withRateLimitRetry } from "../rate-limit-retry.js";
import { exec, execSync } from "node:child_process";
import { existsSync, realpathSync } from "node:fs";
import { hydrateWorktreeDb } from "../worktree-db-hydrate.js";
import { classifyTaskWorktree, isUsableTaskWorktree } from "../worktree-pool.js";
import { classifyTaskWorktree, describeRegisteredWorktrees, isUsableTaskWorktree } from "../worktree-pool.js";
import { classifyStaleLock, tryRemoveStaleLock } from "../worktree-stale-lock.js";
import { executingTaskLock } from "../active-session-registry.js";
@@ -263,6 +264,7 @@ export const mockedExistsSync = vi.mocked(existsSync);
export const mockedRealpathSync = vi.mocked(realpathSync);
export const mockedHydrateWorktreeDb = vi.mocked(hydrateWorktreeDb);
export const mockedClassifyTaskWorktree = vi.mocked(classifyTaskWorktree);
export const mockedDescribeRegisteredWorktrees = vi.mocked(describeRegisteredWorktrees);
export const mockedIsUsableTaskWorktree = vi.mocked(isUsableTaskWorktree);
export const mockedClassifyStaleLock = vi.mocked(classifyStaleLock);
export const mockedTryRemoveStaleLock = vi.mocked(tryRemoveStaleLock);

View File

@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { execSync, spawnSync } from "node:child_process";
import { afterEach, describe, expect, it } from "vitest";
import { hasRequiredWorktreeFiles, isUsableTaskWorktree } from "../worktree-pool.js";
import { classifyTaskWorktree, hasRequiredWorktreeFiles, isUsableTaskWorktree } from "../worktree-pool.js";
const hasGit = spawnSync("git", ["--version"], { stdio: "pipe" }).status === 0;
const describeIfGit = hasGit ? describe : describe.skip;
@@ -81,6 +81,87 @@ describeIfGit("worktree liveness gating (FN-4682)", () => {
await expect(isUsableTaskWorktree(rootDir, worktreePath)).resolves.toBe(true);
});
it.each([
{
name: "ok",
setup: () => {
const rootDir = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "init"');
}));
const worktreePath = track(makeWorktree(rootDir, "ok"));
return { rootDir, worktreePath };
},
expected: { ok: true } as const,
},
{
name: "missing",
setup: () => {
const rootDir = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "init"');
}));
const worktreePath = join(tmpdir(), `fn-4682-missing-${Date.now()}`);
return { rootDir, worktreePath };
},
expected: {
ok: false,
classification: "missing",
reason: "worktree directory does not exist",
} as const,
},
{
name: "incomplete",
setup: () => {
const rootDir = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "init"');
}));
const worktreePath = track(mkdtempSync(join(tmpdir(), "fn-4682-incomplete-")));
return { rootDir, worktreePath };
},
expected: {
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
} as const,
},
{
name: "unregistered",
setup: () => {
const rootDir = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "init"');
}));
const worktreePath = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "standalone"');
}));
return { rootDir, worktreePath };
},
expected: {
ok: false,
classification: "unregistered",
reason: "not registered in git worktree list",
} as const,
},
{
name: "outside-work-tree",
setup: () => {
const rootDir = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "init"');
}));
const worktreePath = track(makeWorktree(rootDir, "outside-work-tree"));
rmSync(join(worktreePath, ".git"), { recursive: true, force: true });
writeFileSync(join(worktreePath, ".git"), "gitdir: /tmp/nonexistent\n", "utf-8");
return { rootDir, worktreePath };
},
expected: {
ok: false,
classification: "outside-work-tree",
reason: "git rev-parse --is-inside-work-tree returned false",
} as const,
},
])("FN-4935: classifyTaskWorktree %s", async ({ setup, expected }) => {
const { rootDir, worktreePath } = setup();
await expect(classifyTaskWorktree(rootDir, worktreePath)).resolves.toEqual(expected);
});
it("FN-4682: rejects missing worktree directory", async () => {
const rootDir = track(makeRepo((dir) => {
git(dir, 'git commit --allow-empty -m "init"');