feat(FN-5479): fix identity-guard merger bypass on detached HEAD and gate l

The merge delivers the FN-5483 identity-guard bypass for merger-driven commits on detached HEAD, plus Steps 2 and 6 of FN-5479 which gate the limbo counter by enqueue acceptance and document the associated invariant. It also restores the dashboard's PWA and theme-boot contract in index.html. New reg

Fusion-Task-Id: FN-5479
This commit is contained in:
Fusion (runfusion.ai)
2026-05-22 08:30:34 -07:00
committed by gsxdsm
parent 4b484fd818
commit 1bffa22ca9
15 changed files with 138 additions and 40 deletions

View File

@@ -122,6 +122,7 @@ vi.mock("../worktree-pool.js", async (importOriginal) => {
});
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
IDENTITY_GUARD_BYPASS_ENV: "FUSION_MERGER_BYPASS_IDENTITY_GUARD",
}));
vi.mock("../worktree-stale-lock.js", async () => {

View File

@@ -123,4 +123,29 @@ describe("FN-4999 reliability interactions: completion-handoff-limbo", () => {
.filter((value: unknown) => typeof value === "number");
expect(increments).toEqual([1, 2, 3]);
});
it("FN-5479: does not consume limbo recovery budget when merge requeue is not accepted", async () => {
const task = makeTask({
status: undefined,
review: undefined,
reviewState: undefined,
mergeDetails: undefined,
completionHandoffLimboRecoveryCount: 2,
log: [{ action: "Task marked done by agent", timestamp: new Date(Date.now() - 6 * 60_000).toISOString() } as any],
});
const store = createStore(task);
const manager = new SelfHealingManager(store, {
rootDir: "/repo",
enqueueMerge: vi.fn(() => false),
requeueForAutoMerge: vi.fn(() => false),
});
await manager.recoverCompletionHandoffLimbo();
expect(store.enqueueMergeQueue).toHaveBeenCalledWith("FN-4999-T");
expect(store.updateTask).not.toHaveBeenCalledWith("FN-4999-T", expect.objectContaining({ completionHandoffLimboRecoveryCount: 3 }));
expect(store.recordRunAuditEvent).not.toHaveBeenCalledWith(expect.objectContaining({ mutationType: "task:auto-recover-completion-handoff-limbo" }));
expect(store.logEntry).not.toHaveBeenCalledWith("FN-4999-T", expect.stringMatching(/Auto-recovered \(FN-4999\)/));
expect(store._get().completionHandoffLimboRecoveryCount).toBe(2);
});
});

View File

@@ -13,6 +13,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
IDENTITY_GUARD_BYPASS_ENV: "FUSION_MERGER_BYPASS_IDENTITY_GUARD",
}));
import { AgentSemaphore } from "../concurrency.js";

View File

@@ -13,10 +13,12 @@ import { installTaskWorktreeIdentityGuard } from "../worktree-hooks.js";
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
IDENTITY_GUARD_BYPASS_ENV: "FUSION_MERGER_BYPASS_IDENTITY_GUARD",
}));
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
IDENTITY_GUARD_BYPASS_ENV: "FUSION_MERGER_BYPASS_IDENTITY_GUARD",
}));
// ── Shared test fixtures ──────────────────────────────────────────────

View File

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

View File

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

View File

@@ -34,6 +34,7 @@ vi.mock("../branch-conflicts.js", () => ({
}));
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: installGuardMock,
IDENTITY_GUARD_BYPASS_ENV: "FUSION_MERGER_BYPASS_IDENTITY_GUARD",
}));
vi.mock("../worktree-stale-lock.js", () => ({
StaleWorktreeIndexLockError: class StaleWorktreeIndexLockError extends Error {

View File

@@ -32,6 +32,23 @@ describe("worktree-hooks", () => {
expect(hook).toContain(`EXPECTED_BRANCH=\"${expectedBranch}\"`);
});
it("honors the merger bypass marker on detached HEAD before computing EXPECTED_BRANCH", () => {
const hook = buildIdentityGuardHook("FN-5483");
const bypassIndex = hook.indexOf('FUSION_MERGER_BYPASS_IDENTITY_GUARD:-');
const expectedBranchIndex = hook.indexOf('EXPECTED_BRANCH=');
const refuseIndex = hook.indexOf('refusing commit');
expect(bypassIndex).toBeGreaterThan(-1);
// bypass check must come before the branch comparison and before the refusal printf
expect(bypassIndex).toBeLessThan(expectedBranchIndex);
expect(bypassIndex).toBeLessThan(refuseIndex);
// bypass must require the exact value "1" — not a non-empty truthy check
expect(hook).toContain('"${FUSION_MERGER_BYPASS_IDENTITY_GUARD:-}" = "1"');
// bypass arm must short-circuit with exit 0 before reaching the refusal path
const bypassBlock = hook.slice(bypassIndex, expectedBranchIndex);
expect(bypassBlock).toContain("exit 0");
});
it("builds commit-msg trailer hook with expected lines", () => {
const hook = buildCommitMsgTrailerHook("FN-42");
expect(hook).toContain("#!/bin/sh");