feat(FN-4640): complete Step 2 — add sandbox run auditor domain

Fusion-Task-Id: FN-4640
Fusion-Task-Lineage: 4a91265f-1714-4854-b08d-7ddb06074253
This commit is contained in:
Fusion (runfusion.ai)
2026-05-15 15:55:50 -07:00
committed by gsxdsm
parent ef7eb2f3ea
commit b28b1949a6
2 changed files with 98 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
import { describe, expect, it } from "vitest";
import type { RunAuditEventInput, TaskStore } from "@fusion/core";
import { createRunAuditor } from "../run-audit.js";
class AuditStoreStub {
events: RunAuditEventInput[] = [];
recordRunAuditEvent(event: RunAuditEventInput): void {
this.events.push(event);
}
}
describe("createRunAuditor sandbox domain", () => {
it("records sandbox events with metadata", async () => {
const store = new AuditStoreStub();
const auditor = createRunAuditor(store as unknown as TaskStore, {
runId: "run-1",
agentId: "agent-1",
taskId: "FN-1",
phase: "execute",
});
await auditor.sandbox({
type: "sandbox:run",
target: "native",
metadata: { timeoutMs: 12000, exitCode: 0 },
});
expect(store.events).toHaveLength(1);
expect(store.events[0]).toMatchObject({
runId: "run-1",
agentId: "agent-1",
taskId: "FN-1",
domain: "sandbox",
mutationType: "sandbox:run",
target: "native",
});
expect(store.events[0].metadata).toMatchObject({
phase: "execute",
timeoutMs: 12000,
exitCode: 0,
});
});
it("no-ops when context is null", async () => {
const store = new AuditStoreStub();
const auditor = createRunAuditor(store as unknown as TaskStore, null);
await auditor.sandbox({ type: "sandbox:prepare", target: "native" });
expect(store.events).toHaveLength(0);
});
it("no-ops when store lacks recordRunAuditEvent", async () => {
const storeWithoutAudit = {} as TaskStore;
const auditor = createRunAuditor(storeWithoutAudit, {
runId: "run-2",
agentId: "agent-2",
taskId: "FN-2",
});
await expect(
auditor.sandbox({ type: "sandbox:failure", target: "native", metadata: { errorMessage: "boom" } }),
).resolves.toBeUndefined();
});
});

View File

@@ -140,6 +140,8 @@ export type FilesystemMutationType =
| "session:write"
| "session:delete";
export type SandboxMutationType = "sandbox:prepare" | "sandbox:run" | "sandbox:failure" | "sandbox:fallback";
/** Input for a git-domain audit event. */
export interface GitAuditInput {
type: GitMutationType;
@@ -167,6 +169,15 @@ export interface FilesystemAuditInput {
metadata?: Record<string, unknown>;
}
/** Input for a sandbox-domain audit event. */
export interface SandboxAuditInput {
type: SandboxMutationType;
/** Target of the mutation (e.g., backend id). */
target: string;
/** Optional structured metadata. */
metadata?: Record<string, unknown>;
}
/** Interface for emitting run-audit events. */
export interface RunAuditor {
/** Emit a git-domain audit event. No-op if no run context is available. */
@@ -175,6 +186,8 @@ export interface RunAuditor {
database(input: DatabaseAuditInput): Promise<void>;
/** Emit a filesystem-domain audit event. No-op if no run context is available. */
filesystem(input: FilesystemAuditInput): Promise<void>;
/** Emit a sandbox-domain audit event. No-op if no run context is available. */
sandbox(input: SandboxAuditInput): Promise<void>;
}
/**
@@ -194,6 +207,7 @@ export function createRunAuditor(store: TaskStore, context: EngineRunContext | n
git: async () => { /* no-op */ },
database: async () => { /* no-op */ },
filesystem: async () => { /* no-op */ },
sandbox: async () => { /* no-op */ },
};
}
@@ -206,6 +220,7 @@ export function createRunAuditor(store: TaskStore, context: EngineRunContext | n
git: async () => { /* no-op */ },
database: async () => { /* no-op */ },
filesystem: async () => { /* no-op */ },
sandbox: async () => { /* no-op */ },
};
}
@@ -270,6 +285,24 @@ export function createRunAuditor(store: TaskStore, context: EngineRunContext | n
};
await store.recordRunAuditEvent(eventInput);
},
sandbox: async (input: SandboxAuditInput) => {
const eventInput: RunAuditEventInput = {
taskId: context.taskId,
agentId: context.agentId,
runId: context.runId,
domain: "sandbox",
mutationType: input.type,
target: input.target,
metadata: {
phase: context.phase,
...(context.source ? { source: context.source } : {}),
...(context.taskLineageId ? { taskLineageId: context.taskLineageId } : {}),
...input.metadata,
},
};
await store.recordRunAuditEvent(eventInput);
},
};
}