ADR-0033 — TheSports is the single score authority; Roanuz is retired
- Status: Accepted
- Date: 2026-08-27
- Supersedes: Roanuz as a live cricket data source
- Related: ADR-0032, ADR-0035,
docs/architecture/data-plane-integration.md§3, §8
Context
Hannibal has carried two cricket data providers. Roanuz predates TheSports and owns the ball-by-ball UI path end to end. TheSports was integrated in August 2026 as a four-sport display feed (cricket, tennis, soccer, basketball).
Roanuz is already off in every environment — ROANUZ_ENABLED=false in dev, dev1, dev2,
staging and prod, verified in the running containers and on disk 2026-08-22; the code
default is false; backend/src/exchanges/index.ts:267 is the only construction site, so
getRoanuzProvider() returns undefined and every consumer routes through that one
function. Zero Roanuz API or WebSocket traffic in 24h across all four running environments.
But ball-by-ball is still Roanuz-wired, and therefore dark. It has no second feed:
routes/cricket.ts:125 resolves to Roanuz, gets null, and returns a 404 that hides the
whole live-feed section. Real users hit this — prod 1 subscribe → 404, dev 20 → 404.
Meanwhile everything needed to replace it exists and has zero production callers:
ballGlyph(), describeBall(), currentRound() and formatIncidentMinute() in
strykr-fe/src/lib/theSportsDetail.ts, the 28px ball-pill strip in LiveFeedSection.tsx,
and the CricketBall / CricketInningsTimeline types. Cricket timeline[].overs and
.wickets are flowing over MQTT right now.
Running two score authorities is not a hedge. It is two decoders, two identity schemes, two sets of coverage assumptions and an unanswerable question whenever they disagree.
Decision
TheSports is the sole authority for score, phase, per-period splits, timeline, ball-by-ball and match statistics, for all four sports. Roanuz is retired.
Betfair and Bifrost remain the sole authority for fixture existence, orientation, markets,
prices, exposure and settlement. The data plane is structurally barred from all of those:
TheSportsDataAdapter implements IMultiSportDataProvider, which has no odds, order or
settlement surface, and cannot join the exchange ProviderRegistry (typed to
IExchangeAdapter). The firewall is a type, not a naming convention.
The cutover runs in dependency order:
- Replace the
resolveToRoanuzgate inroutes/cricket.tswith a TheSports match resolution offthesports_fixture_links. - Repoint
LiveFeedSectionfrom Roanuz'sCricketBallUpdateto TheSportstimeline[].overs/.wickets, throughdecodeBallsPerRound()—timeline[]position 2 is "number of rounds", not overs, and The Hundred uses 5-ball rounds. - Re-home or retire the non-ball-by-ball consumers of
ballByBallService. - Delete
adapters/roanuz/and theROANUZ_*config block. - Revoke the Roanuz credentials at the provider and strip them from every
.env.
Roanuz may be deleted only when all five of these are true:
- cricket ball-by-ball renders from TheSports on a live fixture, observed;
- no route, service or job calls
getRoanuzProvider(); matchGradeService/tossAnalysisService/cricketAnalysisServiceeach have a TheSports source or are explicitly retired as products;- player photos have a source or an explicit fallback (TheSports
player/list.logois 59% populated for cricket; the Roanuz team-images JSON asset is currently the fallback); - credentials revoked.
Consequences
tossAnalysisServicehas no replacement feed and must be retired, not ported. TheSports carries no toss information on any endpoint, for any sport. This is a product decision that the cutover forces and that nobody has yet made.- Cricket ball-by-ball becomes subject to the three-gate chain (ADR-0032): a fixture must be classified and linked before a ball can be drawn on it.
- The 404 that currently hides the live-feed tab is separately wrong in its copy
(
routes/cricket.ts:135claims "Only major international matches are supported"). That is a user-facing defect with its own ticket, deliberately not bundled into step 1. - Dormant provider credentials in five
.envfiles become a credential-hygiene item with an owner.