docs: record that the event log is the source of truth, and why

#45 asked whether a fitted `History` can be persisted, and noted that
the absence of `serde` "reads as an omission rather than a decision,
which invites exactly this issue from every consumer in turn". Fair.

Documents the decision on `History` along with the reasoning that makes
it one: `converge` reaches a fixed point determined by the events,
ratings and configuration alone, so a snapshot would carry no
information the event log does not — it would cache the computation,
never the answer.

It also bounds what a snapshot could buy, since that is the question a
consumer actually has. Re-converging an unchanged history costs one
iteration, measured at 0.91 ms against 365 ms cold on 2 000 events, so
it would make a cold restart cheap and do nothing for appends. Appending
one event moves its participants more than a sigma across their whole
history, so that re-convergence is real work rather than repeated work.

Closes #45

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
2026-09-08 01:22:28 +02:00
co-authored by Claude Opus 5
parent 35d7512557
commit 2cf21a753d
+26
View File
@@ -189,6 +189,32 @@ impl CompetitorConfig {
} }
} }
/// A fitted history: competitors, their skills over time, and the events that
/// produced them.
///
/// # Persistence
///
/// `History` is deliberately not serializable, and the event log is the
/// intended source of truth. This is a decision rather than an omission.
///
/// [`converge`](History::converge) reaches a fixed point determined by the
/// events, ratings and configuration alone — not by the message state it
/// started from, which ingestion resets anyway. So a snapshot of a fitted
/// history would carry no information the event log does not; it would be a
/// cache of the *computation*, never of the answer.
///
/// That also bounds what one could buy. Re-converging an unchanged history
/// costs a single iteration — measured at 0.91 ms against 365 ms cold on 2 000
/// events — so a snapshot would make a cold restart cheap. It would do nothing
/// for appends: adding one event moves its participants by more than a sigma
/// across their whole history, back to their first appearance, so the
/// re-convergence is real work rather than repeated work. Cost per append is
/// inherently O(history). A design that made appends cheap would be computing a
/// filtering estimate — see [`History::filtered_learning_curve`] — rather than
/// Through Time.
///
/// `tests/reconvergence_equivalence.rs` pins the path-independence this rests
/// on.
pub struct History< pub struct History<
T: Time = i64, T: Time = i64,
D: Drift<T> = ConstantDrift, D: Drift<T> = ConstantDrift,