diff --git a/packages/engine/src/__tests__/_project-engine-merge-lane-fixture.ts b/packages/engine/src/__tests__/_project-engine-merge-lane-fixture.ts new file mode 100644 index 0000000000..9a1577df88 --- /dev/null +++ b/packages/engine/src/__tests__/_project-engine-merge-lane-fixture.ts @@ -0,0 +1,62 @@ +type MergeLaneState = { + mergeQueue: string[]; + mergeActive: Set; + capacityDeferredMergeTaskIds: Set; + capacityDeferredMergeReasons: Map; + capacityDeferredMerges: Map; + coordinatorAdmittedMergeTaskIds: Set; + pausedReviewTaskIds: Set; + mergeRunning: boolean; + mergeRunningSince: number; + activeMergeSession: { dispose(): void } | null; + activeMergeTaskId: string | null; + activeMergeStartedAtMs: number | null; + mergeBodyInFlight: Promise | null; + mergeAbortController: AbortController | null; + mergeRetryTimer: ReturnType | null; + prMergeRetryTimers: Map>; + workspaceBusyReenqueues: Map; + workspaceBusyReenqueueTimers: Set>; + manualMergeResolvers: Map; + shuttingDown: boolean; + startupGeneration: number; + started: boolean; +}; + +/** + * FNXC:MergeQueue 2026-08-09-06:22: + * Object.create(ProjectEngine.prototype) runs no class field initializers, so a prototype-only + * merge fake starts with every merge-lane field undefined. FN-8871 requires this fixture to include + * capacity and PR-retry merge state, preventing production drain additions from drifting test fakes. + */ +export function seedMergeLaneState( + engine: T, + overrides: Partial = {}, +): T & MergeLaneState { + const defaults: MergeLaneState = { + mergeQueue: [], + mergeActive: new Set(), + capacityDeferredMergeTaskIds: new Set(), + capacityDeferredMergeReasons: new Map(), + capacityDeferredMerges: new Map(), + coordinatorAdmittedMergeTaskIds: new Set(), + pausedReviewTaskIds: new Set(), + mergeRunning: false, + mergeRunningSince: 0, + activeMergeSession: null, + activeMergeTaskId: null, + activeMergeStartedAtMs: null, + mergeBodyInFlight: null, + mergeAbortController: null, + mergeRetryTimer: null, + prMergeRetryTimers: new Map(), + workspaceBusyReenqueues: new Map(), + workspaceBusyReenqueueTimers: new Set(), + manualMergeResolvers: new Map(), + shuttingDown: false, + startupGeneration: 0, + started: true, + }; + + return Object.assign(engine, defaults, overrides); +} diff --git a/packages/engine/src/__tests__/group-merge-coordinator.test.ts b/packages/engine/src/__tests__/group-merge-coordinator.test.ts index e4828e8c75..b0fca6872f 100644 --- a/packages/engine/src/__tests__/group-merge-coordinator.test.ts +++ b/packages/engine/src/__tests__/group-merge-coordinator.test.ts @@ -26,6 +26,7 @@ import { } from "../merge/group-merge-coordinator.js"; import { ProjectEngine } from "../project-engine.js"; import { runAiMerge } from "../merge/merger-ai.js"; +import { seedMergeLaneState } from "./_project-engine-merge-lane-fixture.js"; const dirs: string[] = []; @@ -1490,14 +1491,12 @@ describe("resolveBranchGroupMergeRouting", () => { getSessionStats: vi.fn(() => ({ tokens: { input: 1, output: 1 } })), }, })); - engine.manualMergeResolvers = new Map(); - engine.mergeActive = new Set(); - engine.mergeQueue = []; - engine.capacityDeferredMergeTaskIds = new Set(); - engine.capacityDeferredMerges = new Map(); - engine.coordinatorAdmittedMergeTaskIds = new Set(); - engine.started = true; - engine.shuttingDown = false; + /* + FNXC:SharedBranchMemberHold 2026-08-09-05:22: + FN-8811's release half runs the production drain, so this prototype fake must carry complete + merge-lane state rather than only the fields the test happened to seed when it was written. + */ + seedMergeLaneState(engine); const released = await ProjectEngine.prototype.onMerge.call(engine, "FN-3324"); createResolvedAgentSessionMock.mockReset();