builtbykev 7d579fd8c1 Repair the MLB timing contract at its producer seam
A prop carrying a usable game_time skipped bindGame — and bindGame is where
game_date was assigned. So the fast path left props TIMED and DATELESS. Once
PropLine began supplying game_time (historically it did not, which is why the
binder exists), every prop took that branch.

PROVEN ON PRODUCTION, attempt acq_972544a1-563e-47ba-bde4-2f73ecf536bd:
12,015 props acquired live, alreadyHad 12,015, dates_requested 0, schedule games
0, unresolved 12,015 (no_schedule), admitted 0, graded 0, no product writes.

THE DERIVATION IS NOT INVENTED HERE. bindGame already defines exactly this for
the already-timed case — `game_date: etDate(prop.game_time)` — and
ledgerService.gameDateFor already PREFERS the derived ET date over a
provider-supplied one (`dateET(prop.game_time) || prop.game_date`). etDate and
dateET are the same America/New_York en-CA formatter. This applies the contract
the fast path skipped; it does not add a new one.

Repaired at the PRODUCER, not by teaching consumers to compensate. The prop
already has an exact game_time, so the date is derived deterministically — the
props are NOT sent back through event rebinding to obtain it. Canonical event
resolution remains solely responsible for proving WHICH game, which is what
matters for doubleheaders, provider nesting defects and team contradictions.

SCOPE, locked by tests:
  valid game_time + missing game_date   -> filled
  valid game_time + matching game_date  -> preserved
  valid game_time + DIFFERING game_date -> preserved, NOT silently rewritten
                                           (future-hardening territory)
  invalid/absent game_time              -> no date invented; existing binder
                                           fallback semantics unchanged
  game_time itself                      -> never modified

UTC DATE IS NOT THE BASEBALL DATE, and the tests prove it: 01:45Z -> 2026-08-27,
03:10Z -> 2026-08-27, plus a DST-boundary pair (2026-11-01 01:30Z -> 10-31 EDT,
06:30Z -> 11-01 EST). No hardcoded offset; the repository helper does the work.

DOUBLEHEADER: both halves derive the SAME game_date — expected, and what makes
the schedule fetch possible — while the UNMODIFIED resolver still separates them
by clock into gamePk 824514 / 824478.

Ten teeth, injections verified present, against a green baseline of 129:
assignment removed (9) · UTC date instead of ET (5) · already-timed props forced
through rebinding (4) · conflicting date overwritten (2) · invalid time given a
fabricated date (1) · doubleheader guard weakened (1) · unresolved admitted (6) ·
game_time modified on the bound path (2). Restored byte-identically.

TWO TEETH LANDED AND PASSED FIRST TIME — coverage holes, not safe defects. The
doubleheader fixture matched both games exactly, so deleting the >1h guard
changed nothing; and no test reached the bound path at all. Added a stray-time
refusal case and a bound-path case, then re-ran both failing.

Admission still fails closed: UNRESOLVED / AMBIGUOUS / CONTRADICTED, and RESOLVED
without a canonical id, are all still rejected. The gate protected production
during the outage and is untouched.

ONE FILE, SIX BEHAVIOURAL LINES. eventIdentity, gradeSlateService,
snapshotService, retentionService, oddsService, bookRoles, analyzeViaEngine1,
probabilityEstimator, acquisitionTrace and snapshotScheduler all UNCHANGED.

390 suites / 5,310 tests pass. web tsc exit 0. Lineage stays OFF.

The historical 03:00 cause remains UNPROVEN — that run was served from cache and
its game_date state was never recorded. Consistency is not proof.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
2026-08-28 03:15:58 -04:00
S
Description
No description provided
35 MiB
Languages
JavaScript 68.3%
TypeScript 14.4%
HTML 11.5%
Python 4.6%
CSS 0.6%
Other 0.5%