FN-8917: add telemetry seams to merger test stores

Ensure merger test fixtures model session-usage telemetry so user-hold release coverage exercises the production lane.

- Add emitUsageEvent fakes across merger AI test stores.
- Assert the user-held branch-group member emits merger session telemetry when explicitly released.
- Document fixture requirements and expected hold-path stderr.

Files changed:
 .../src/__tests__/group-merge-coordinator.test.ts   | 21 +++++++++++++++++++++
 .../engine/src/__tests__/merger-ai-cleanup.test.ts  |  1 +
 .../merger-ai-dependency-install.slow.test.ts       |  1 +
 .../__tests__/merger-ai-push-after-merge.test.ts    |  1 +
 .../src/__tests__/merger-ai-renamed-columns.test.ts |  6 ++++++
 .../ai-merge-cleanup-enoent-idempotent.test.ts      |  1 +
 6 files changed, 31 insertions(+)

Fusion-Task-Id: FN-8917

Fusion-Task-Lineage: aab96cdb-50ec-441f-80f5-944e8e1d70ab

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-09 16:09:55 -07:00
parent 6bd8004ba1
commit 9ce698699a
6 changed files with 31 additions and 0 deletions

View File

@@ -1318,8 +1318,14 @@ function createPostReviewStore(task: Record<string, any>, branchGroup: Record<st
The production drain calls these methods for token-budget enforcement and branch-group promotion
evaluation inside catch-and-warn wrappers. Supply the real, budget-free seams so a missing method
cannot silently remove coverage while leaving this prototype fixture green.
FNXC:BranchGroupAutoMergeGate 2026-08-09-22:51:
The production merger drain emits session-start telemetry in both its review and merge passes.
Telemetry failures are swallowed by design, so this fake must implement emitUsageEvent or a missing
seam degrades coverage into warnings that the user-hold regression guard catches.
*/
getSettingsByScope: vi.fn(async () => ({ global: {}, project: settings })),
emitUsageEvent: vi.fn(async () => true),
listTasksByBranchGroup: vi.fn(async () => (branchGroup ? [task] : [])),
getBranchGroup: vi.fn(() => branchGroup),
updateTask: vi.fn(async (_id: string, patch: Record<string, unknown>) => Object.assign(task, patch)),
@@ -1477,6 +1483,11 @@ describe("resolveBranchGroupMergeRouting", () => {
expect(held).toMatchObject({ merged: false, noOp: true });
expect(blockedMerge).not.toHaveBeenCalled();
expect(git(repo, "git rev-parse main")).toBe(mainBefore);
/*
FNXC:BranchGroupAutoMergeGate 2026-08-09-22:51:
The expected fatal-path stderr is the proof that the automatic hold did not land this member;
it is not a missing fixture path.
*/
expect(() => git(repo, "git show mission/M-8811:user-hold-feature.txt")).toThrow();
let mergeAttempts = 0;
@@ -1539,6 +1550,16 @@ describe("resolveBranchGroupMergeRouting", () => {
}
expect(offendingWarnings, "merge drain emitted missing-store-seam warnings").toEqual([]);
/*
FNXC:BranchGroupAutoMergeGate 2026-08-09-22:51:
An empty warning list is insufficient when a future path skips telemetry entirely. Assert the
production merger lane exercised the fake-store seam during the explicit release.
*/
expect(store.emitUsageEvent).toHaveBeenCalledWith(expect.objectContaining({
kind: "session_start",
category: "agent-session",
meta: expect.objectContaining({ lane: "merger" }),
}));
expect(released.merged).toBe(true);
expect(store.listTasksByBranchGroup).toHaveBeenCalledWith("BG-user-hold");
expect(mergeAttempts).toBe(1);

View File

@@ -111,6 +111,7 @@ function makeStore(taskId = "FN-1") {
emit: vi.fn(),
logEntry: vi.fn(async (_id: string, message: string) => { logs.push(message); }),
appendAgentLog: vi.fn(async (_id: string, message: string) => { logs.push(message); }),
emitUsageEvent: vi.fn(async () => true),
recordRunAuditEvent: vi.fn(async (event: any) => { audits.push(event); }),
};
return { store, audits, logs };

View File

@@ -54,6 +54,7 @@ function makeStore(settingsOverrides: Record<string, unknown> = {}) {
emit: vi.fn(),
logEntry: vi.fn(async () => undefined),
appendAgentLog: vi.fn(async () => undefined),
emitUsageEvent: vi.fn(async () => true),
};
return store;
}

View File

@@ -113,6 +113,7 @@ function makeStore(settingsOverrides: Record<string, unknown> = {}) {
emit: vi.fn(),
logEntry: vi.fn(async (_id: string, message: string, action?: string) => { logs.push({ message, action }); }),
appendAgentLog: vi.fn(async (_id: string, message: string) => { logs.push({ message }); }),
emitUsageEvent: vi.fn(async () => true),
getBranchGroup: vi.fn(() => null),
recordRunAuditEvent: vi.fn(),
};

View File

@@ -102,6 +102,12 @@ function storeWith(current: Task, ir: WorkflowIr | undefined): TaskStore {
updateTask: vi.fn(async () => current),
moveTask: vi.fn(async () => current),
logEntry: vi.fn(async () => undefined),
/*
FNXC:MergeQueue 2026-08-09-22:51:
A fake store that drives runAiMerge into the merger AI lane must expose emitUsageEvent because
session telemetry is defensive in production. Stores rejected at the workspace guard do not reach it.
*/
emitUsageEvent: vi.fn(async () => true),
recordRunAuditEvent: vi.fn(async () => undefined),
getTaskWorkflowSelection: vi.fn(() => selection),
getTaskWorkflowSelectionAsync: vi.fn(async () => selection),

View File

@@ -63,6 +63,7 @@ function makeStore(taskId: string, branch: string) {
emit: vi.fn(),
logEntry: vi.fn(async (_id: string, message: string) => { logs.push(message); }),
appendAgentLog: vi.fn(async (_id: string, message: string) => { logs.push(message); }),
emitUsageEvent: vi.fn(async () => true),
recordRunAuditEvent: vi.fn(async (event: any) => { audits.push(event); }),
};
return { store, task, audits, logs };