From 2cf21a753d9c9d232eba0f57a2ba386b22037c3f Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Tue, 8 Sep 2026 01:22:28 +0200 Subject: [PATCH] docs: record that the event log is the source of truth, and why MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #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) Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ --- src/history.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/history.rs b/src/history.rs index 562f317..d0bd959 100644 --- a/src/history.rs +++ b/src/history.rs @@ -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< T: Time = i64, D: Drift = ConstantDrift,