Skip to main content

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}