import type { EventBus } from '../kernel/events.js'; /** * ToolAuditLog — idea #9 from IDEAS.md. * * Tamper-evident audit trail for tool calls. Every tool_use / * tool_result pair is appended to a sidecar JSONL with a chained * SHA-256 — each entry's `prevHash` is the prior entry's `hash`, * so any post-hoc modification of a single line breaks the chain * from that point forward. * * Why a sidecar (not the session JSONL)? * Same reason as `AnnotationsStore` and `ReplayLogStore`: the * session log is an event-sourced journal. Mixing in a hash * chain would inflate every read and tightly couple the * integrity check to the event format. Sidecar keeps both * concerns orthogonal. * * What "tamper-evident" means here: * - The hash covers the full serialized entry: tool name, id, * input, output, timestamp, author. Changing any byte * changes the hash. * - The chain is sequential — a verifier walks the file in * order, recomputing each hash, and checks `prevHash` * matches the previous entry's `hash`. * - Any insertion, deletion, or modification of a single * entry surfaces as a "chain broken at entry N" verdict. * * What it does NOT defend against: * - An attacker who rewrites the whole file consistently. * For that you'd need an external anchor (signing key, * transparency log, etc.) — out of scope for Phase 1. * - The agent itself misbehaving; this is post-hoc audit, not * real-time enforcement. Use `PermissionPolicy` for that. * * File layout: `sessionScopedPath(dir, sessionId, '.audit.jsonl')`, * one entry per line. The chain starts with a `genesis` entry whose * `prevHash` is all zeros. */ export interface AuditEntry { /** Monotonic index (0-based). */ index: number; /** UUID for cross-referencing with logs. */ id: string; /** ISO timestamp. */ ts: string; /** Hash of the previous entry (or all-zeros for the genesis entry). */ prevHash: string; /** Hash of this entry's content (sha256 over the canonical JSON). */ hash: string; toolName: string; toolUseId: string; input: unknown; output: unknown; isError: boolean; } export type VerifyResult = { ok: true; entries: number; } | { ok: false; brokenAt: number; reason: string; }; export interface ToolAuditLogOptions { /** Root sessions directory used with `sessionScopedPath(..., '.audit.jsonl')`. */ dir: string; /** * Flush the file system cache to disk every N writes per session. * Default 100. Lower values = better crash durability, more I/O overhead. * Set to `Infinity` to disable periodic fsync (fastest, but highest data-loss risk). */ fsyncEvery?: number | undefined; events?: EventBus; traceId?: string; } export declare class ToolAuditLog { private readonly dir; private readonly events; private readonly traceId; /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */ private readonly tailHash; /** In-memory counter for entry indices — avoids re-reading the file on every write. */ private readonly tailIndex; /** * File mtime+size recorded after our last write, per session. Used to * detect cross-process writes (session handoff, recovery) that would * invalidate the in-memory tail cache: if the stat no longer matches * we re-read the file to re-establish the chain tip before appending. */ private readonly tailStat; /** Tracks writes since last fsync, per session. */ private readonly unSyncedWrites; private readonly writeChains; private readonly fsyncEvery; constructor(opts: ToolAuditLogOptions); /** * Append a tool call/result pair to the chain. Returns the * resulting entry. Idempotency is not guaranteed — if you * record the same tool_use twice you get two entries. That's * intentional: the audit log is a record, not a cache. */ record(input: { sessionId: string; toolName: string; toolUseId: string; input: unknown; output: unknown; isError: boolean; }): Promise; /** * Resolve the chain tip (previous hash + next index) for an append. * Uses the in-memory `tailHash`/`tailIndex` cache when the file's * stat matches our last known write; falls back to a full read on * cache miss or when an external writer has extended the file. */ private _resolveChainTip; /** * Walk the chain and verify every entry's hash and prevHash. * Returns a structured verdict — never throws. */ verify(sessionId: string): Promise; /** All entries for a session, in insertion order. */ load(sessionId: string): Promise; private filePath; private readAll; /** * Tracks writes since last fsync and triggers periodic fsync. * Called after each O(1) append to maintain the same durability * guarantees as the old writeAll approach. */ private _trackUnsynced; /** * Explicitly sync the file to disk. Called automatically every * `fsyncEvery` writes, and available for callers who want to * force a sync before closing or during graceful shutdown. */ flush(sessionId: string): Promise; private sync; private enqueue; } //# sourceMappingURL=tool-audit-log.d.ts.map