Skip to main content

EventSink

Struct EventSink 

Source
pub struct EventSink { /* private fields */ }
Expand description

Per-session event fanout + audit. Emits to the registered emitter (WS subscribers) and journals the audit-relevant subset to a JSONL event log.

Implementations§

Source§

impl EventSink

Source

pub fn new( session_id: impl Into<String>, emitter: Option<EventEmitter>, journal_path: Option<PathBuf>, ) -> Self

Source

pub fn test_sink() -> Self

A sink that drops everything — unit tests that don’t assert on events.

Source

pub fn collecting(session_id: &str) -> (Self, Arc<Mutex<Vec<CoderEvent>>>)

Collect events into a shared Vec — tests that DO assert on events.

Source

pub fn emit(&self, kind: CoderEventKind) -> CoderEvent

Source

pub fn record_turn_completed( &self, decision: &str, stop_reason: Option<&str>, was_truncated: bool, turns: u32, model: &str, models_served: &[String], )

Append a durable TurnCompleted audit record for a coder-loop terminal.

The coder loop has no Runtime in scope (only this sink), so this mirrors car_engine::Runtime::record_turn_completed directly onto the coder session journal — the same EventKind::TurnCompleted + data shape the assistant path emits (via the shared car_engine::goal::turn_completed_data), so the coder-path false-success / truncation / turn-budget-burn signal is captured in the exact form the harness miners already understand.

Consumption is a separate follow-up, NOT done here: these events land in the coder session journal (<state_dir>/<session_id>.events.jsonl), a durable record read offline / via the FFI diagnose_from_jsonl. The in-process daemon miners (harness_adapt::diagnose, evolution::failed_trace_events) run over session.runtime.log (the assistant path), so they do NOT yet consume this coder journal — wiring it into the daemon evolution path is tracked separately. Journal-only: not a WS-streamed CoderEvent, matching how P0b kept TurnCompleted off the live AssistantEvent stream (no WS/FFI surface change).

Source

pub fn record_model_fallback(&self, from: &str, to: &str, reason: &str)

Journal one mid-run backbone change.

Written directly rather than recognized in EventSink::audit, once per distinct hop rather than once per session. The live ModelFallback event is behind a once-per-phase latch so the stream does not narrate every routing decision — right for a stream, wrong for a record. A run that degraded twice has two facts, and journaling off the latched emit would keep only the first.

The caller supplies the deduplication, because the fact worth recording is a distinct transition and a persistent condition re-reports the same one on every turn. This method itself always appends: it is the record, not the policy.

car#1333 recorded WHO wrote each turn (models_served on the turn terminal). This records WHY the backbone moved, which is a genuinely different fact rather than the same one twice: without it, a run that silently degraded to another model looks — to harness_adapt-style mining and to a human reading the journal — like the code under test behaving badly (car#1351).

Journal-only, no WS/FFI surface, same as the two above.

Source

pub fn record_gate_verdict(&self, kind: EventKind, data: HashMap<String, Value>)

Journal one merge-gate verdict.

Written here rather than derived from the event stream, deliberately. The obvious shape is to bridge the verdict out as a foreman: "gate" CoderEvent and have EventSink::audit recognize it — one path, live and durable together. That is forgeable. process_stream fires the emitter on every line the supervised CLI prints, and StreamEvent’s #[serde(flatten)] extra absorbs arbitrary top-level keys and re-emits them at top level — so a single line of stdout from the model being supervised satisfies any predicate audit could key on, and writes “the gate accepted this patch” into the audit record with the gate never having run. It would not even be scoped to foreman sessions: audit runs for every coder session with a journal.

car#1243 is what makes that fatal rather than untidy. The patches this gate rules on are authored on peers this host does not control, which is an argument for a record the audited party CANNOT write to. So the live foreman: "gate" event stays narration — spoofable, and only ever read as narration — and the durable record is written straight to the journal from the foreman loop, on the same shape as EventSink::record_turn_completed above and for the same reason.

kind is the gate’s own EventKind::GateAccepted / EventKind::GateRejected, and data is its payload verbatim. Note the gate treats Inconclusive (verify timed out, or not configured) as not-accepted, so “we don’t know” journals as GateRejected carrying outcome: "inconclusive" — the kind is binary, the payload is not.

Journal-only, no WS/FFI surface: same as record_turn_completed.

Source

pub fn authoring_models(&self) -> Vec<String>

Every model that completed a turn in this session, from the journal, distinct and in first-seen order.

record_turn_completed already writes model_id on every terminal native path, so this reads what is there rather than adding a second record that could disagree with it.

A set, not the last one. TurnCompleted is a PER-ITERATION terminal, and an unpinned session is free to route each iteration differently — strict_model is cfg.model.is_some(), so an unpinned chain may also degrade to another model on an outage. A reviewer judges the accumulated worktree diff, not the last iteration, so taking the last name would let the model that wrote most of the change review it as long as something else finished the run.

Mid-iteration models count too (car#1333). A terminal names only the model that reached it, so a model that wrote turns 1-3 of an iteration another model finished used to leave no record at all — and could then sit on the panel reviewing what it had written. models_served carries the rest, and both are folded here.

A record written before that field existed simply has no models_served, and folds to its model_id alone — the same answer it gave before.

Empty for a run with no native turns — a foreman or external session farms to a coding CLI whose backbone CAR never resolved, so there is no honest answer. Empty means “nobody asked CAR’s own loop to write this”, NOT “nobody wrote it”.

Source

pub fn events(&self) -> Vec<Event>

Source

pub fn record_proactive_memory( &self, maintenance: &ProactiveMaintenanceReport, decision: &ProactiveMemoryDecision, )

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more