salvor_replay/summary.rs
1//! [`RunSummary`]: the one-line-per-run projection of a store's log.
2//!
3//! The type lives here, with the event model, rather than in the store crate
4//! whose trait returns it. It is a plain value (an id, a count, two
5//! timestamps) with no storage machinery behind it, so a consumer that only
6//! renders or transports a run listing can name it without linking a database
7//! driver.
8
9use serde::{Deserialize, Serialize};
10use time::OffsetDateTime;
11
12use crate::id::RunId;
13
14/// A one-line-per-run projection of the log, returned by
15/// `EventStore::list_runs`.
16///
17/// Every field is a pure aggregate over the store's queryable columns
18/// (`COUNT`, `MIN`, `MAX`), computed without parsing a single envelope or
19/// replaying anything. That is the reason the field set is small and stops
20/// where it does:
21///
22/// - [`run_id`](Self::run_id) names the run. Required.
23/// - [`event_count`](Self::event_count) is how many events the run has, a
24/// cheap `COUNT(*)` and a useful "how far along" signal.
25/// - [`first_recorded_at`](Self::first_recorded_at) and
26/// [`last_recorded_at`](Self::last_recorded_at) are the earliest and latest
27/// recorded timestamps, a `MIN`/`MAX` that gives a run's start and its age
28/// or most recent activity.
29///
30/// Run *status* (running, suspended, completed, and so on) is deliberately
31/// absent. Status is a replay-time projection: you derive it by folding the
32/// log, not by reading a column. Putting it here would either require the
33/// store to parse and interpret events (which is the replay engine's job) or
34/// to keep a denormalized status column in step with the log (a second source
35/// of truth). Both break the rule that the log is the only state, so status
36/// stays out of the store and lives in the replay layer that owns it.
37///
38/// The timestamps are reconstructed in UTC. Fidelity to the exact recorded
39/// offset lives in the stored envelope JSON; this summary normalizes to UTC
40/// because it exists for sorting and display, not for round-tripping.
41#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
42pub struct RunSummary {
43 /// The run this summary describes.
44 pub run_id: RunId,
45 /// How many events the run's log holds.
46 pub event_count: u64,
47 /// The earliest recorded timestamp in the run, in UTC.
48 #[serde(with = "time::serde::rfc3339")]
49 pub first_recorded_at: OffsetDateTime,
50 /// The latest recorded timestamp in the run, in UTC.
51 #[serde(with = "time::serde::rfc3339")]
52 pub last_recorded_at: OffsetDateTime,
53}