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:
@@ -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", () => {
|
||||
|
||||
@@ -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 }));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user