feat(FN-5544): emit runtime-resolved audit event across engine lanes

Adds a "session runtime resolved" audit event that flows through the engine's main execution lanes — triage, executor, reviewer, merger, heartbeat, step-session-executor, and mission-execution-loop — with runtime mutation support and test coverage, plus a compile-fix for the merger auditor wiring.

Fusion-Task-Id: FN-5544

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5544
This commit is contained in:
gsxdsm
2026-05-23 12:06:37 -07:00
parent cd09d1925f
commit 7a20b95502
13 changed files with 329 additions and 1 deletions

View File

@@ -126,6 +126,97 @@ describe("createResolvedAgentSession", () => {
}),
);
});
it("emits session:runtime-resolved when runAuditor is provided", async () => {
const mockSession = { prompt: vi.fn() } as any;
const createSessionMock = vi.fn().mockResolvedValue({ session: mockSession });
const auditDatabaseMock = vi.fn().mockResolvedValue(undefined);
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
await createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: "mock",
defaultModelId: "mock-default",
runAuditor: { database: auditDatabaseMock } as any,
settings: { testMode: true } as any,
});
expect(createSessionMock).not.toHaveBeenCalled();
expect(auditDatabaseMock).toHaveBeenCalledTimes(1);
expect(auditDatabaseMock).toHaveBeenCalledWith({
type: "session:runtime-resolved",
target: "mock",
metadata: {
sessionPurpose: "executor",
runtimeId: "mock",
wasConfigured: true,
provider: "mock",
modelId: "mock-default",
mockProviderActive: true,
testModeActive: true,
},
});
});
it("succeeds when runAuditor is omitted", async () => {
const mockSession = { prompt: vi.fn() } as any;
const createSessionMock = vi.fn().mockResolvedValue({
session: mockSession,
sessionFile: "session.json",
});
resolveRuntimeMock.mockResolvedValue({
runtime: {
id: "pi",
name: "Default PI Runtime",
createSession: createSessionMock,
promptWithFallback: vi.fn(),
describeModel: vi.fn(() => "mock/model"),
},
runtimeId: "pi",
wasConfigured: false,
});
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
await expect(createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
})).resolves.toMatchObject({ runtimeId: "pi", wasConfigured: false });
});
it("warns and continues when runAuditor throws", async () => {
const mockSession = { prompt: vi.fn() } as any;
const createSessionMock = vi.fn().mockResolvedValue({ session: mockSession });
resolveRuntimeMock.mockResolvedValue({
runtime: {
id: "pi",
name: "Default PI Runtime",
createSession: createSessionMock,
promptWithFallback: vi.fn(),
describeModel: vi.fn(() => "mock/model"),
},
runtimeId: "pi",
wasConfigured: false,
});
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
await expect(createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
runAuditor: {
database: vi.fn().mockRejectedValue(new Error("audit down")),
} as any,
})).resolves.toMatchObject({ session: mockSession, runtimeId: "pi", wasConfigured: false });
warnSpy.mockRestore();
});
});
describe("resolveMergerSessionModel", () => {

View File

@@ -0,0 +1,76 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { createResolvedAgentSession } from "../agent-session-helpers.js";
import { createRunAuditor } from "../run-audit.js";
import { MOCK_PROVIDER_ID } from "../providers/mock-provider.js";
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 };
});
/** SessionPurpose canonical set: executor | triage | reviewer | merger | heartbeat | validation */
describe("FN-5544 session:runtime-resolved audit event", () => {
let recordedEvents: RunAuditEvent[] = [];
let counter = 0;
let store: TaskStore;
beforeEach(() => {
recordedEvents = [];
counter = 0;
resolveRuntimeMock.mockReset().mockResolvedValue({
runtime: {
id: "pi",
name: "pi",
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) => {
recordedEvents.push({ ...input, id: `audit-${++counter}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) => {
const filtered = recordedEvents.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType);
return filter?.limit ? filtered.slice(0, filter.limit) : filtered;
}),
} as unknown as TaskStore;
});
it("emits mock-provider runtime-resolved event", async () => {
const auditor = createRunAuditor(store, { runId: "r1", agentId: "a1", taskId: "FN-5544", phase: "execute", source: "executor" });
await createResolvedAgentSession({ sessionPurpose: "executor", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: MOCK_PROVIDER_ID, defaultModelId: "scripted", runAuditor: auditor });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
expect(events[0]?.target).toBe("mock");
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "executor", runtimeId: "mock", mockProviderActive: true }));
});
it("emits non-mock provider metadata", async () => {
const auditor = createRunAuditor(store, { runId: "r2", agentId: "a1", taskId: "FN-5544", phase: "review", source: "reviewer" });
await createResolvedAgentSession({ sessionPurpose: "reviewer", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: "openai", defaultModelId: "gpt-4.1", runAuditor: auditor, runtimeHint: "pi" });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "reviewer", provider: "openai", modelId: "gpt-4.1", mockProviderActive: false }));
});
it("records no rows when runAuditor is omitted", async () => {
await createResolvedAgentSession({ sessionPurpose: "validation", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: MOCK_PROVIDER_ID, defaultModelId: "scripted" });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(0);
});
it("round-trips metadata through getRunAuditEvents", async () => {
const auditor = createRunAuditor(store, { runId: "r4", agentId: "a1", taskId: "FN-5544", phase: "heartbeat", source: "heartbeat" });
await createResolvedAgentSession({ sessionPurpose: "heartbeat", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5", runtimeHint: "hermes", runAuditor: auditor, settings: { testMode: true } as any });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "heartbeat", runtimeHint: "hermes", testModeActive: true }));
});
});