import { type AcquireLockOptions } from '../fs/lock.js'; export type AuditDecision = 'allow' | 'deny' | 'confirm_required'; export interface AuditEntry { seq: number; ts: number; prev_hash: string; kind: string; portal: string; payload: unknown; decision: AuditDecision; reason?: string; sig?: string; } export interface StoredAuditEntry extends AuditEntry { hash: string; } export declare const ZERO_HASH: string; export declare const HASH_HEX_LEN = 64; export declare class AuditChainError extends Error { readonly atSeq: number | undefined; constructor(msg: string, atSeq?: number); } /** * Canonical JSON serialization: keys sorted lexicographically, recursive. * This is what we hash, so it must be deterministic across engine * versions and across re-serializations. */ export declare function canonicalJSON(value: unknown): string; /** * Compute the keccak256 hash of an entry (without the `hash` field). * Returns lowercase hex. */ export declare function hashEntry(entry: AuditEntry): string; /** * Stamp an entry with its hash. */ export declare function sealEntry(entry: AuditEntry): StoredAuditEntry; /** * Serialize a stored entry to a single JSON line (newline-terminated). */ export declare function serializeEntry(entry: StoredAuditEntry): string; /** * Parse a single line and verify its self-hash. Does NOT check chain linkage. */ export declare function parseLine(line: string): StoredAuditEntry; /** * Verify a buffer's worth of JSONL audit entries. * - Empty buffer → empty chain. * - Each entry must self-hash correctly. * - Each entry's seq must be expected (0, 1, 2, ...). * - Each entry's prev_hash must match the previous entry's hash. * - A trailing non-empty fragment (no terminating newline) is a torn write. * * Returns the entries in order. Throws AuditChainError on any failure. */ export declare function verifyChain(buf: Buffer | string): StoredAuditEntry[]; /** * The head state of an audit chain: enough to append the next entry. */ export interface ChainHead { nextSeq: number; prevHash: string; } /** * Read the chain head from an existing audit file. Verifies the whole chain * during read. If the file does not exist or is empty, returns the genesis head. */ export declare function readHead(path: string): ChainHead; /** * Append-only audit writer with fsync after every write. * * Typical use: * const w = new AuditWriter('/path/to/audit.log'); * w.append({ kind: 'eth_sign_message', portal: 'evm:bot', payload, decision: 'allow', sig }); * w.close(); * * The seq and prev_hash fields are managed by the writer; callers supply * everything else. * * Multiple processes (one sigil-mcp per Claude session) share one audit file, * so every append serializes through the sidecar lock directory * `.lock.d` (see src/fs/lock.ts). The in-memory head is only a cache: * under the lock, the writer checks the file's byte length and the hash of * its last line against what it cached and, if either moved, re-reads (and * re-verifies) the chain before computing the next entry. Without this, concurrent writers * each extend their own stale tail and the interleaved lines fail startup * verification with seq gaps and broken prev_hash links. */ export interface AuditWriterOpts { /** Clock override for tests. Defaults to Date.now. */ now?: () => number; lock?: AcquireLockOptions; /** * What to do when the file on disk fails chain verification: * - 'throw' (default): raise AuditChainError; the caller decides. * - 'quarantine': move the file to `.corrupt-` (never * overwriting an existing file), report via `warn`, and start a fresh * chain. Nothing is deleted. sigil-mcp uses this so one damaged log * can't lock the user out of signing until they hand-edit it. */ onCorrupt?: 'throw' | 'quarantine'; warn?: (message: string) => void; } export declare class AuditWriter { #private; readonly path: string; readonly lockPath: string; constructor(path: string, opts?: AuditWriterOpts); get head(): ChainHead; append(input: { kind: string; portal: string; payload: unknown; decision: AuditDecision; reason?: string; sig?: string; }): StoredAuditEntry; close(): void; } /** * Test-only seams. `quarantineStep` is invoked before each step of * quarantine publication and may throw to simulate that step failing; * `tailScanMax` overrides the scan cap so the fallback path can be tested * without writing tens of megabytes. Never set by production code. */ export declare const _auditTestHooks: { quarantineStep?: (step: 'fsync-evidence' | 'fsync-dir' | 'unlink') => void; tailScanMax?: number; }; //# sourceMappingURL=log.d.ts.map