/** * audit.ts — append-only NDJSON audit log of every meaningful step. * * Every write tool, every detective dispatch, every gate failure goes here. * The chief-detective trail is what makes the Investigation Bureau auditable * (per ADR-002 + agentic-layer-spec sec 5). */ import { appendFile, mkdir } from "node:fs/promises"; import path from "node:path"; import { env } from "./env"; const ensured = new Set(); async function ensureDir(p: string): Promise { if (ensured.has(p)) return; await mkdir(p, { recursive: true }); ensured.add(p); } export interface AuditEvent { event: string; job_id?: string; detective?: string; /** Free-form payload — keep keys snake_case. */ [k: string]: unknown; } export async function audit(ev: AuditEvent): Promise { const row = { ts: new Date().toISOString(), worker_id: env.WORKER_ID, ...ev }; const dir = path.dirname(env.AUDIT_LOG); try { await ensureDir(dir); await appendFile(env.AUDIT_LOG, JSON.stringify(row) + "\n", "utf-8"); } catch (e) { // Audit must never break the worker. Log to stderr and move on. console.error(`[audit] append failed: ${(e as Error).message}`); } }