feat(FN-4948): complete Step 2 — wire identity guard into worktree provisioning

Fusion-Task-Id: FN-4948
Fusion-Task-Lineage: dc622643-4c3e-4217-9219-a6a6e5424427
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 13:26:41 -07:00
committed by gsxdsm
parent f42a149fe3
commit 487dfcc10c
10 changed files with 67 additions and 35 deletions

View File

@@ -1,4 +1,5 @@
import { vi } from "vitest";
import { installTaskWorktreeIdentityGuard } from "../worktree-hooks.js";
// Mock external dependencies
vi.mock("../pi.js", () => ({
@@ -115,6 +116,10 @@ vi.mock("../worktree-pool.js", async (importOriginal) => {
isUsableTaskWorktree: vi.fn().mockResolvedValue(true),
};
});
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("../worktree-stale-lock.js", async () => {
const actual = await vi.importActual<typeof import("../worktree-stale-lock.js")>("../worktree-stale-lock.js");
return {
@@ -268,6 +273,7 @@ export const mockedDescribeRegisteredWorktrees = vi.mocked(describeRegisteredWor
export const mockedIsUsableTaskWorktree = vi.mocked(isUsableTaskWorktree);
export const mockedClassifyStaleLock = vi.mocked(classifyStaleLock);
export const mockedTryRemoveStaleLock = vi.mocked(tryRemoveStaleLock);
export const mockedInstallTaskWorktreeIdentityGuard = vi.mocked(installTaskWorktreeIdentityGuard);
export type EventListener = (...args: unknown[]) => void;
@@ -349,7 +355,9 @@ export function resetExecutorMocks() {
});
mockedClassifyStaleLock.mockReset();
mockedTryRemoveStaleLock.mockReset();
mockedInstallTaskWorktreeIdentityGuard.mockReset();
mockedClassifyStaleLock.mockResolvedValue({ kind: "fresh", reason: "fresh" } as any);
mockedInstallTaskWorktreeIdentityGuard.mockResolvedValue(undefined);
mockedTryRemoveStaleLock.mockResolvedValue({ removed: true });
mockExecuteAll.mockResolvedValue([]);
mockTerminateAllSessions.mockResolvedValue(undefined);

View File

@@ -10,6 +10,10 @@
* - Crash scenarios are handled gracefully (semaphore release, status cleanup)
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
}));
import { AgentSemaphore } from "../concurrency.js";
const WAIT_FOR_ASYNC_OPTIONS = { timeout: 2000, interval: 5 };

View File

@@ -10,6 +10,10 @@ import { AgentLogger } from "../agent-logger.js";
import * as worktreeBackendModule from "../worktree-backend.js";
import type { TaskDetail, Settings, TaskStore } from "@fusion/core";
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
}));
// ── Shared test fixtures ──────────────────────────────────────────────
function makePrompt(steps: string[]): string {

View File

@@ -1,4 +1,8 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
}));
import { acquireTaskWorktree } from "../worktree-acquisition.js";
import type { WorktreeBackend } from "../worktree-backend.js";

View File

@@ -8,6 +8,9 @@ const { execMock, existsSyncMock } = vi.hoisted(() => {
vi.mock("node:child_process", () => ({ exec: execMock }));
vi.mock("node:fs", () => ({ existsSync: existsSyncMock }));
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("../worktree-pool.js", async () => {
const actual = await vi.importActual<any>("../worktree-pool.js");
return { ...actual, isUsableTaskWorktree: vi.fn().mockResolvedValue(true) };

View File

@@ -8,7 +8,7 @@ import {
RemovalReason,
} from "../worktree-backend.js";
const { execMock, accessMock, existsSyncMock, parseIndexLockPathMock, classifyStaleLockMock, tryRemoveStaleLockMock } = vi.hoisted(() => {
const { execMock, accessMock, existsSyncMock, parseIndexLockPathMock, classifyStaleLockMock, tryRemoveStaleLockMock, installGuardMock } = vi.hoisted(() => {
const mock = vi.fn();
(mock as any)[Symbol.for("nodejs.util.promisify.custom")] = mock;
return {
@@ -18,6 +18,7 @@ const { execMock, accessMock, existsSyncMock, parseIndexLockPathMock, classifySt
parseIndexLockPathMock: vi.fn(),
classifyStaleLockMock: vi.fn(),
tryRemoveStaleLockMock: vi.fn(),
installGuardMock: vi.fn(),
};
});
@@ -27,6 +28,9 @@ vi.mock("node:fs/promises", () => ({ access: accessMock }));
vi.mock("../branch-conflicts.js", () => ({
inspectBranchConflict: vi.fn().mockResolvedValue({ kind: "stale" }),
}));
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: installGuardMock,
}));
vi.mock("../worktree-stale-lock.js", () => ({
StaleWorktreeIndexLockError: class StaleWorktreeIndexLockError extends Error {
lockPath: string;
@@ -54,6 +58,8 @@ beforeEach(() => {
parseIndexLockPathMock.mockReset();
classifyStaleLockMock.mockReset();
tryRemoveStaleLockMock.mockReset();
installGuardMock.mockReset();
installGuardMock.mockResolvedValue(undefined);
parseIndexLockPathMock.mockReturnValue(null);
classifyStaleLockMock.mockResolvedValue({ kind: "fresh", reason: "fresh" });
tryRemoveStaleLockMock.mockResolvedValue({ removed: true });
@@ -77,6 +83,7 @@ describe("NativeWorktreeBackend", () => {
'git worktree add -b "fusion/fn-1" "/repo/.worktrees/fn-1" "main"',
expect.objectContaining({ cwd: "/repo", timeout: 120000, maxBuffer: 10485760 }),
);
expect(installGuardMock).toHaveBeenCalledWith({ worktreePath: "/repo/.worktrees/fn-1", taskId: "FN-1" });
});
it("retries with suffix and resolves", async () => {