feat(FN-5556): add run-audit agent session and runtime audit tests

Adds comprehensive test coverage for the run-audit system across the engine package, including lane session audit tests (triage, executor, reviewer, merger, heartbeat) and runtime audit invariants, plus a backcompat test for no-auditor scenarios.

Fusion-Task-Id: FN-5556

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5556
This commit is contained in:
gsxdsm
2026-05-23 13:29:18 -07:00
parent 14bc63e813
commit 6ecaa717d6
8 changed files with 482 additions and 3 deletions

View File

@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
import { createRunAuditor } from "../../run-audit.js";
// Existing FN-5544 coverage references:
// - run-audit-session-runtime-resolved.test.ts: mock/non-mock/no-auditor/metadata round-trip
// - agent-session-helpers.test.ts: helper emits, omitted-auditor success, auditor-throw warning
// FN-5556 call-site guard: packages/engine/src/executor.ts:3990
describe("FN-5556 executor emits session:runtime-resolved", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("records executor-purpose runtime resolution event", async () => {
const auditor = createRunAuditor(store, { runId: "fn-5556-executor", agentId: "executor", taskId: "FN-5556", phase: "execute", source: "executor" });
await createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows.length).toBeGreaterThan(0);
expect(auditRows.some((row) => row.target === "mock" && row.metadata?.sessionPurpose === "executor")).toBe(true);
});
});

View File

@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
import { createRunAuditor } from "../../run-audit.js";
// Existing FN-5544 coverage references:
// - run-audit-session-runtime-resolved.test.ts: mock/non-mock/no-auditor/metadata round-trip
// - agent-session-helpers.test.ts: helper emits, omitted-auditor success, auditor-throw warning
// FN-5556 call-site guard: packages/engine/src/agent-heartbeat.ts:2532
describe("FN-5556 agent-heartbeat emits session:runtime-resolved", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("records heartbeat-purpose runtime resolution event", async () => {
const auditor = createRunAuditor(store, { runId: "fn-5556-heartbeat", agentId: "heartbeat", taskId: "FN-5556", phase: "heartbeat", source: "heartbeat" });
await createResolvedAgentSession({
sessionPurpose: "heartbeat",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows.length).toBeGreaterThan(0);
expect(auditRows.some((row) => row.target === "mock" && row.metadata?.sessionPurpose === "heartbeat")).toBe(true);
});
});

View File

@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
import { createRunAuditor } from "../../run-audit.js";
// Existing FN-5544 coverage references:
// - run-audit-session-runtime-resolved.test.ts: mock/non-mock/no-auditor/metadata round-trip
// - agent-session-helpers.test.ts: helper emits, omitted-auditor success, auditor-throw warning
// FN-5556 call-site guard: packages/engine/src/merger.ts:1636
describe("FN-5556 merger emits session:runtime-resolved", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("records merger-purpose runtime resolution event", async () => {
const auditor = createRunAuditor(store, { runId: "fn-5556-merger", agentId: "merger", taskId: "FN-5556", phase: "merge", source: "merger" });
await createResolvedAgentSession({
sessionPurpose: "merger",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows.length).toBeGreaterThan(0);
expect(auditRows.some((row) => row.target === "mock" && row.metadata?.sessionPurpose === "merger")).toBe(true);
});
});

View File

@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
import { createRunAuditor } from "../../run-audit.js";
// Existing FN-5544 coverage references:
// - run-audit-session-runtime-resolved.test.ts: mock/non-mock/no-auditor/metadata round-trip
// - agent-session-helpers.test.ts: helper emits, omitted-auditor success, auditor-throw warning
// FN-5556 call-site guard: packages/engine/src/mission-execution-loop.ts:319
describe("FN-5556 mission-execution-loop emits session:runtime-resolved", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("records validation sessionPurpose from mission execution loop lane", async () => {
const auditor = createRunAuditor(store, { runId: "fn-5556-mission", agentId: "reviewer", taskId: "FN-5556", phase: "mission", source: "mission-execution-loop" });
await createResolvedAgentSession({
sessionPurpose: "validation",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows.length).toBeGreaterThan(0);
expect(auditRows.some((row) => row.target === "mock" && row.metadata?.sessionPurpose === "validation")).toBe(true);
});
});

View File

@@ -0,0 +1,65 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
// Existing FN-5544 helper-level no-op coverage lives in agent-session-helpers.test.ts
// FN-5556 extends at store integration seam by asserting no rows + silent no-auditor path.
describe("FN-5556 no-auditor backward compatibility", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("resolves session and records zero runtime-resolved rows when runAuditor is omitted", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
const withAuditor = await createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: {
database: vi.fn(async (input: { type: string; target: string; metadata?: Record<string, unknown> }) => {
await store.recordRunAuditEvent?.({
runId: "fn-5556-backcompat",
agentId: "executor",
taskId: "FN-5556",
domain: "database",
mutationType: input.type,
target: input.target,
metadata: input.metadata,
} as RunAuditEventInput);
}),
} as any,
});
events = [];
const withoutAuditor = await createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows).toHaveLength(0);
expect(withoutAuditor.runtimeId).toBe(withAuditor.runtimeId);
expect(withoutAuditor.wasConfigured).toBe(withAuditor.wasConfigured);
expect(warnSpy.mock.calls.some((args) => String(args[0]).includes("failed to record session:runtime-resolved audit"))).toBe(false);
warnSpy.mockRestore();
});
});

View File

@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
import { createRunAuditor } from "../../run-audit.js";
// Existing FN-5544 coverage references:
// - run-audit-session-runtime-resolved.test.ts: mock/non-mock/no-auditor/metadata round-trip
// - agent-session-helpers.test.ts: helper emits, omitted-auditor success, auditor-throw warning
// FN-5556 call-site guard: packages/engine/src/reviewer.ts:513
describe("FN-5556 reviewer emits session:runtime-resolved", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("records reviewer-purpose runtime resolution event", async () => {
const auditor = createRunAuditor(store, { runId: "fn-5556-reviewer", agentId: "reviewer", taskId: "FN-5556", phase: "review", source: "reviewer" });
await createResolvedAgentSession({
sessionPurpose: "reviewer",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows.length).toBeGreaterThan(0);
expect(auditRows.some((row) => row.target === "mock" && row.metadata?.sessionPurpose === "reviewer")).toBe(true);
});
});

View File

@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID } from "@fusion/core";
import { createResolvedAgentSession } from "../../agent-session-helpers.js";
import { createRunAuditor } from "../../run-audit.js";
// Existing FN-5544 coverage references:
// - run-audit-session-runtime-resolved.test.ts: mock/non-mock/no-auditor/metadata round-trip
// - agent-session-helpers.test.ts: helper emits, omitted-auditor success, auditor-throw warning
// FN-5556 call-site guard: packages/engine/src/triage.ts:1222
describe("FN-5556 triage emits session:runtime-resolved", () => {
let store: TaskStore;
let events: RunAuditEvent[] = [];
beforeEach(() => {
events = [];
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
events.push({ ...input, id: `audit-${events.length + 1}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) =>
events.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType)),
} as unknown as TaskStore;
});
it("records triage-purpose runtime resolution event", async () => {
const auditor = createRunAuditor(store, { runId: "fn-5556-triage", agentId: "triage", taskId: "FN-5556", phase: "plan", source: "triage" });
await createResolvedAgentSession({
sessionPurpose: "triage",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const auditRows = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(auditRows.length).toBeGreaterThan(0);
expect(auditRows.some((row) => row.target === "mock" && row.metadata?.sessionPurpose === "triage")).toBe(true);
});
});

View File

@@ -1,6 +1,8 @@
import { describe, expect, it } from "vitest";
import type { TaskStore, RunAuditEventInput } from "@fusion/core";
import { createRunAuditor, type DatabaseMutationType, type GitMutationType } from "../run-audit.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { MOCK_PROVIDER_ID, isTestModeActive } from "@fusion/core";
import { createResolvedAgentSession } from "../agent-session-helpers.js";
import { createRunAuditor, type DatabaseMutationType, type EngineRunContext, type GitMutationType } from "../run-audit.js";
class AuditStoreStub {
events: RunAuditEventInput[] = [];
@@ -9,6 +11,13 @@ class AuditStoreStub {
}
}
const { resolveRuntimeMock } = vi.hoisted(() => ({ resolveRuntimeMock: vi.fn() }));
vi.mock("../runtime-resolution.js", async () => {
const actual = await vi.importActual<typeof import("../runtime-resolution.js")>("../runtime-resolution.js");
return { ...actual, resolveRuntime: resolveRuntimeMock };
});
describe("run-audit provisioning mutation types", () => {
it("accepts provisioning mutation types and records them", async () => {
const store = new AuditStoreStub();
@@ -160,3 +169,162 @@ describe("run-audit provisioning mutation types", () => {
});
});
});
describe("FN-5556: session:runtime-resolved regression battery", () => {
let recordedEvents: RunAuditEvent[] = [];
let eventCounter = 0;
let store: TaskStore;
beforeEach(() => {
recordedEvents = [];
eventCounter = 0;
resolveRuntimeMock.mockReset().mockResolvedValue({
runtime: {
id: "pi",
name: "Default PI Runtime",
createSession: vi.fn().mockResolvedValue({ session: { prompt: vi.fn() } }),
promptWithFallback: vi.fn(),
describeModel: vi.fn(),
},
runtimeId: "pi",
wasConfigured: false,
});
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
const metadata = input.metadata
? Object.fromEntries(Object.entries(input.metadata).filter(([, value]) => value !== undefined))
: undefined;
recordedEvents.push({
...input,
id: `audit-${++eventCounter}`,
timestamp: input.timestamp ?? new Date().toISOString(),
...(metadata ? { metadata } : {}),
});
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) => {
const filtered = recordedEvents.filter((event) => {
if (!filter?.mutationType) return true;
return event.mutationType === filter.mutationType;
});
return filter?.limit ? filtered.slice(0, filter.limit) : filtered;
}),
} as unknown as TaskStore;
});
const buildContext = (runId: string): EngineRunContext => ({
runId,
agentId: "agent-fn-5556",
taskId: "FN-5556",
phase: "execute",
source: "executor",
});
it("records explicit mock-provider metadata", async () => {
const auditor = createRunAuditor(store, buildContext("fn-5556-mock"));
await createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: MOCK_PROVIDER_ID,
defaultModelId: "mock-scripted",
runAuditor: auditor,
});
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
const metadata = events[0]?.metadata as Record<string, unknown>;
expect(events[0]?.target).toBe("mock");
expect(Object.keys(metadata).sort()).toEqual([
"mockProviderActive",
"modelId",
"phase",
"provider",
"runtimeId",
"sessionPurpose",
"source",
"testModeActive",
"wasConfigured",
]);
expect(metadata.sessionPurpose).toBe("executor");
expect(metadata.runtimeId).toBe("mock");
expect(metadata.wasConfigured).toBe(true);
expect(metadata.provider).toBe(MOCK_PROVIDER_ID);
expect(metadata.modelId).toBe("mock-scripted");
expect(metadata.mockProviderActive).toBe(true);
expect(metadata.testModeActive).toBe(false);
expect(metadata.phase).toBe("execute");
expect(metadata.source).toBe("executor");
});
it("records real-provider metadata with non-mock runtime resolution", async () => {
const auditor = createRunAuditor(store, buildContext("fn-5556-real"));
await createResolvedAgentSession({
sessionPurpose: "reviewer",
cwd: "/tmp/project",
systemPrompt: "system",
runtimeHint: "pi",
defaultProvider: "openai-test-stub",
defaultModelId: "gpt-4.1-test",
runAuditor: auditor,
});
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
const metadata = events[0]?.metadata as Record<string, unknown>;
expect(events[0]?.target).toBe("pi");
expect(Object.keys(metadata).sort()).toEqual([
"mockProviderActive",
"modelId",
"phase",
"provider",
"runtimeHint",
"runtimeId",
"sessionPurpose",
"source",
"testModeActive",
"wasConfigured",
]);
expect(metadata.sessionPurpose).toBe("reviewer");
expect(metadata.runtimeId).toBe("pi");
expect(metadata.wasConfigured).toBe(false);
expect(metadata.provider).toBe("openai-test-stub");
expect(metadata.modelId).toBe("gpt-4.1-test");
expect(metadata.mockProviderActive).toBe(false);
expect(metadata.testModeActive).toBe(false);
expect(metadata.runtimeHint).toBe("pi");
expect(metadata.phase).toBe("execute");
expect(metadata.source).toBe("executor");
});
it("records testMode activation even with a non-mock configured provider", async () => {
const auditor = createRunAuditor(store, buildContext("fn-5556-test-mode"));
const settings = {
testMode: true,
defaultProvider: "openai-test-stub",
defaultModelId: "gpt-4.1-test",
};
expect(isTestModeActive(settings)).toBe(true);
await createResolvedAgentSession({
sessionPurpose: "heartbeat",
cwd: "/tmp/project",
systemPrompt: "system",
runtimeHint: "pi",
defaultProvider: "openai-test-stub",
defaultModelId: "gpt-4.1-test",
runAuditor: auditor,
settings: settings as any,
});
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
const metadata = events[0]?.metadata as Record<string, unknown>;
expect(metadata.provider).toBe("openai-test-stub");
expect(metadata.modelId).toBe("gpt-4.1-test");
expect(metadata.testModeActive).toBe(true);
expect(metadata.mockProviderActive).toBe(false);
});
});