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:
committed by
gsxdsm
parent
4b484fd818
commit
1bffa22ca9
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user