export interface EventRecord { /** When this event last happened. */ at: number; msg: string; /** * How many times in a row this same message has arrived. Absent means once, * so records written before this existed still read correctly. */ count?: number; /** * What KIND of thing happened (cap-verify, credential-sync, identity-mismatch, * ...), so the log can be filtered by decision rather than by prose. */ kind?: string; /** * The evidence the decision was based on: the numbers, names and verdicts as * they were at that moment. The message stays the human sentence; this is what * lets "why did it do that?" be answered from the log alone instead of by * reproducing the moment. */ data?: Record; /** * Which ccx wrote this line. * * A log read days later has to say which build produced it, or a report of * "it did X" cannot be tied to the code that did X, and a behaviour already * fixed reads as still broken. Absent on records written before this existed. */ v?: string; } /** The structured half of an event, alongside its human-readable message. */ export interface EventDetail { kind?: string; data?: Record; } export declare function eventsFilePath(configHome: string): string; /** * Read the last `limit` events, oldest first, skipping any malformed lines. * * The limit counts records AFTER folding repeats, so asking for five events * gives five things that happened rather than five copies of one of them. */ export declare function readEvents(configHome: string, limit?: number): EventRecord[]; /** * How large the file may get before it is worth trimming, in bytes. * * Measured in BYTES because that is what `stat` gives for free. Counting lines * means reading the whole file, and doing that on every append is how the first * version of this turned a cheap write back into an expensive one: 900 appends * took over twenty seconds. Size is O(1) to ask for, so the common path never * opens the file at all. * * Generous on purpose. Trimming rewrites the file, so it should be rare, and a * few hundred kilobytes of text costs nothing to hold. */ /** Exported so tests assert the real bound rather than a copy of the number. */ export declare const TRIM_BYTES: number; /** * Append one event. * * A TRUE append, one line, and never a read-modify-write of the whole file. * Several ccx processes share this log (a session, the dashboard tailing it, the * editor launcher), and rewriting the file from each of them was wrong in two * ways at once, both reproduced with four concurrent writers: * * - it LOST events. Two writers read the same state and the second rewrite * erased the first one's event. 74% of events disappeared. * - it THREW. The atomic rewrite renames a temp file onto the target, and on * Windows that fails with EPERM when another process is doing the same. Three * of four writers crashed. Nothing here was wrapped, so a log line could take * down a session start or a swap. * * An append cannot collide with another append and needs no temp file, so both * go away. Writing is best effort besides: telemetry must never be able to stop * the thing it is describing. */ export declare function appendEvent(configHome: string, msg: string, now: number, detail?: EventDetail): void; /** * Format an event as `HH:MM message` in local time, with a repeat count when * the same thing has happened more than once in a row. The time shown is the * LAST occurrence, which is the one you want when asking "is this still going". */ export declare function formatEvent(r: EventRecord, previousVersion?: string): string; /** Format a run of events, marking each point where the build changed. */ export declare function formatEvents(records: EventRecord[]): string[];