// engine/audit-trail/logger.ts — Append-only audit logger with SHA-256 hash chain (SPEC-167)
import { createHash, randomUUID } from 'node:crypto';
import { appendFile, mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
import { canonicalJson, readJsonlLines } from './utils.js';
/**
 * Compute SHA-256 hex digest of a string.
 */
export function sha256(input) {
    return createHash('sha256').update(input, 'utf8').digest('hex');
}
/**
 * Read the hash of the last line in a JSONL file.
 * Returns empty string if the file is empty or does not exist.
 */
async function readLastHash(filePath) {
    const lines = await readJsonlLines(filePath);
    if (lines.length === 0) {
        return '';
    }
    const lastLine = lines[lines.length - 1] ?? '';
    try {
        const parsed = JSON.parse(lastLine);
        return typeof parsed.hash === 'string' ? parsed.hash : '';
    }
    catch {
        return '';
    }
}
/**
 * Per-file write queues to prevent race conditions on the hash chain.
 * Node.js is single-threaded but async I/O allows two concurrent `appendEvent`
 * calls to both read the same `lastHash` before either writes, breaking the
 * chain integrity. Chaining each write onto a resolved promise serializes
 * all writes to the same file without blocking the event loop.
 */
const writeQueues = new Map();
/**
 * Perform the actual append operation (must run serially per file).
 */
async function doAppend(filePath, input) {
    await mkdir(dirname(filePath), { recursive: true });
    const previousHash = await readLastHash(filePath);
    const partial = {
        id: randomUUID(),
        timestamp: new Date().toISOString(),
        eventType: input.eventType,
        specId: input.specId,
        userId: input.userId,
        action: input.action,
        details: input.details ?? {},
        previousHash,
    };
    const hash = sha256(canonicalJson(partial));
    const event = { ...partial, hash };
    await appendFile(filePath, JSON.stringify(event) + '\n', 'utf-8');
    return event;
}
/**
 * Append a new audit event to the JSONL log at filePath.
 * Each event's hash is derived from the previous entry's hash, forming a chain.
 * The file is created (with parent dirs) if it does not exist.
 *
 * Writes are serialized per file via a promise queue to prevent race conditions
 * that would break the hash chain when multiple events are appended concurrently.
 */
export function appendEvent(filePath, input) {
    let result;
    const next = (writeQueues.get(filePath) ?? Promise.resolve()).then(async () => {
        result = await doAppend(filePath, input);
    });
    writeQueues.set(filePath, next.then(() => undefined, () => undefined));
    return next.then(() => result);
}
// Re-export canonicalJson so consumers that need the hash logic (e.g., verifier)
// can import it from the module barrel without reaching into utils directly.
export { canonicalJson } from './utils.js';
//# sourceMappingURL=logger.js.map