ADR-0035 — match/detail_live is the only in-play source; match/live/history is a post-match archive
- Status: Accepted
- Date: 2026-08-27
- Related: ADR-0034,
docs/architecture/data-plane-integration.md§4
Context
TheSports exposes two endpoints that return the same three-key shape — score, stats /
timeline, players — and it is tempting to serve both a live scorecard and a finished
one from whichever is convenient. Measured 2026-08-27, they are disjoint:
| endpoint | finished matches | in-play matches |
|---|---|---|
cricket/match/live/history | 23 of 23 (100%) | 0 of 10 (0%) |
cricket/match/detail_live | absent by design | 7–8 in-play rows per snapshot |
live/history is exactly what the provider's own product name says — "Statistical data
(historical matches)". It is a post-match archive. Tennis behaves the same way
(live/history 12/12 on finished, detail_live on live). Basketball's live/history
returns {} for the three live matches probed.
Any design that serves both from one endpoint shows empty scorecards during play — the single worst failure mode for a live-score product, because it is invisible in testing against finished fixtures and total in production during a match.
The coverage.mlive flag does not predict live/history availability and may not be
used to route between them.
A third property compounds this: match/detail_live has no "match ended" message. A
match that finishes is simply absent from the next response. Absence is the signal.
Decision
Phase decides the source, and the phase comes from the match record, not from a guess.
- In-play →
match/detail_live(via the MQTT deltas merged into its snapshot). It is the only source of an in-play score, timeline or scorecard. - Finished →
match/live/history, on demand, byuuid. - Scheduled → the diary record. No live call.
- The terminal status and the final score come from the diary, never from the live feed, because the live feed cannot say a match ended. The recent-window diary cycle exists solely to bound that lag to minutes.
- A live record carries a short TTL (
4 × staleness, minimum 30s) so a match dropping out of the in-play feed stops being reported live on its own. The TTL is refreshed only by re-asking the provider — never by rewriting our own last known state, because a self-keepalive would keep a finished match resident too. That is the stale-live-record bug, reintroduced by our own cache. match/diaryhas a hard ±30-day window (bisected exactly; ±31d returns405 Beyond the scope of account permissions) and defaults silently to today when called with no parameters. Anything older than 30 days must usematch/seasonormatch/list, which carry no such limit. A param-lesscode: 0with empty results is not evidence that an endpoint is healthy.
Consequences
- Two code paths, deliberately, and the branch is on phase — which is derived from the
provider's own
status_idtable, per sport, never shared between sports. - A cricket Test needs a 6-day diary lookback, not one day: the provider files a Test once at day one while the betting side re-dates the fixture as each day's play begins. Measured — a live Test with a full scorecard sat outside every window we pulled and rendered no score for days.
- Any future "just cache the last live state" optimisation is forbidden by rule 5 and the reason must travel with the code, or it will be reintroduced.
- Historical backfill beyond 30 days is a
match/seasonjob, not a diary job.