pub struct EventLog { /* private fields */ }Expand description
Single-writer, append-only handle on a mission’s events.jsonl.
Constructed via EventLog::acquire; the lock file is released on drop.
Implementations§
Source§impl EventLog
impl EventLog
Sourcepub fn acquire(
paths: &MissionPaths,
mission_id: &str,
throttle: Duration,
force: LockForce,
) -> Result<EventLog>
pub fn acquire( paths: &MissionPaths, mission_id: &str, throttle: Duration, force: LockForce, ) -> Result<EventLog>
Acquire the single-writer lock for a mission and open its event log.
Creates the mission directory tree (mission dir, runs/, control/)
if missing — no-follow: a symlinked .kranz/missions/mission dir or
runtime file is refused (P1 mission-path-no-follow), never followed
into another repository’s tree. If the lock file already exists, the
holder’s liveness decides against the LockForce tier (see its
matrix): a provably
dead holder is always stolen; a live or indeterminate one fails with
EngineError::LockHeld naming the holder’s pid unless the tier
permits the steal. Existing events are loaded to resume the seq
counter and to verify mission_id matches the log. Any torn final
line left by a crash is repaired (truncated, or newline-terminated if
the line itself is intact) before the append handle opens, so new
events never glue onto a partial line.
The lock file records up to three lines — <pid>, the acquire time as
unix epoch seconds (diagnostics only), and the holder’s own process
identity token — so a later acquire can detect pid reuse: a holder
whose CURRENT token differs from the recorded one is not the process
that wrote the lock. Tokens are compared for raw equality (see
[process_identity_token]) — never via clock arithmetic, which
wall-clock steps would poison. The legacy one- and two-line formats
are still accepted; they just forgo reuse detection.
Sourcepub fn mission_id(&self) -> &str
pub fn mission_id(&self) -> &str
Mission id this log was acquired for.
Sourcepub fn events_path(&self) -> &Path
pub fn events_path(&self) -> &Path
Path of the underlying events.jsonl.
Sourcepub fn append(&mut self, kind: EventKind) -> Result<Event>
pub fn append(&mut self, kind: EventKind) -> Result<Event>
Append one event: assigns the next seq and the current timestamp,
serializes to a single JSON line, and returns a clone of the stored
event so the caller can broadcast it. If this boundary redacts any
string payload, secret.redacted audit events are appended immediately
after the sanitized event.
Durability: lifecycle events drain any buffered deltas first (file
order == append order), then write + flush + fsync. Stream deltas are
buffered and drained once the oldest buffered delta exceeds the
throttle age — checked here on each append, or on demand (without
waiting for another append) via EventLog::flush_if_due.
Sourcepub fn append_with_redaction_audits(
&mut self,
kind: EventKind,
) -> Result<(Event, Vec<Event>)>
pub fn append_with_redaction_audits( &mut self, kind: EventKind, ) -> Result<(Event, Vec<Event>)>
Append one event and any required secret.redacted audit events.
Returns the sanitized primary event plus the audit events that followed
it, so callers that maintain snapshots can fold the same sequence.
Sourcepub fn append_redacting(
&mut self,
kind: EventKind,
) -> Result<(Event, Vec<SecretFinding>)>
pub fn append_redacting( &mut self, kind: EventKind, ) -> Result<(Event, Vec<SecretFinding>)>
Append one event after scanning/redacting string payloads. Returns the sanitized event plus secret findings (fingerprints only, never values).
Sourcepub fn flush(&mut self) -> Result<()>
pub fn flush(&mut self) -> Result<()>
Write any buffered deltas out to the file (no fsync — deltas are recoverable).
Sourcepub fn buffer_age(&self) -> Option<Duration>
pub fn buffer_age(&self) -> Option<Duration>
Elapsed time since the OLDEST buffered delta, or None when the
buffer is empty.
Sourcepub fn flush_if_due(&mut self) -> Result<bool>
pub fn flush_if_due(&mut self) -> Result<bool>
Drain the buffer to the file, WITHOUT waiting for another append
call, if it is non-empty and has aged past throttle. Gives idle
missions (waiting on an approval gate, worker stopped) a wall-clock-
driven flush instead of leaving deltas buffered indefinitely.
Sourcepub fn read_events(path: &Path) -> Result<Vec<Event>>
pub fn read_events(path: &Path) -> Result<Vec<Event>>
Read and validate the full event log at path.
Seq must start at 1 and increase by exactly 1; any gap or duplicate is
EngineError::LogCorruption. An unparseable FINAL line is a torn
write from a crash and is dropped with a warning; an unparseable line
anywhere else is corruption.
Sourcepub fn read_events_and_log_bytes(path: &Path) -> Result<(Vec<Event>, Vec<u8>)>
pub fn read_events_and_log_bytes(path: &Path) -> Result<(Vec<Event>, Vec<u8>)>
Read the log at path ONCE and return the validated events together
with the exact byte prefix they were parsed from (12th-pass review):
the evidence bundle must ship events.jsonl bytes that reproduce the
chain/cost/escalations it derived, so parsing one snapshot and then
rereading the file for the raw copy is not allowed — a concurrent
append between the two opens would ship bytes the folds never saw.
Torn-tail rule (the honest one): an unparseable FINAL line is dropped from the events AND excluded from the returned bytes — the shipped prefix is exactly what parsed, so the bundle’s log always re-folds to the bundle’s derived files. bytes-shipped == bytes-parsed.
Sourcepub fn read_events_after(path: &Path, after_seq: u64) -> Result<Vec<Event>>
pub fn read_events_after(path: &Path, after_seq: u64) -> Result<Vec<Event>>
Read events with seq > after_seq (WS reconnect / tailing). The whole
log is still validated — a corrupt prefix must not go unnoticed.
Sourcepub fn read_tail_events(path: &Path, max_bytes: u64) -> Result<Vec<Event>>
pub fn read_tail_events(path: &Path, max_bytes: u64) -> Result<Vec<Event>>
Read the events on the last max_bytes of the log WITHOUT reading or
validating the full file — O(tail) I/O for hot callers that only need
trailing facts (e.g. “has a terminal lifecycle event been appended?”).
The window is aligned to the first complete line inside it, and
unparseable lines (a torn final write) are skipped rather than treated
as corruption — callers that need validation use
EventLog::read_events. Returns the whole log when the file fits
inside the window.