Releases
2.0.9
For you
- Each release's own changelog page now gives its two lists the heading level right after the release title, so a screen reader's list of headings no longer skips a level. The page looks the same.
- When a run is marked degraded, the footer now says why, one short sentence per reason, right below when the numbers were last updated, and the MCP prints the same sentences beside the degraded sources. A clean run shows nothing new.
- A rookie whose projection is what earlier rookies at his position scored now has a sentence on his player page naming the history he does not have yet: targets, carries, pass attempts or snap share, and yards per target, carry or pass attempt. His numbers are unchanged.
- The game page's injury list, the rankings status note and the player page now date an injury from nflverse by the official report's own release time. When a run could not read that time, the list and the note say it is unknown and the player page says nothing about when it was reported; none of them shows the time our run read it.
- The track record, the game page's calibration sentence and the MCP now show each graded game call exactly as the track record publishes it, instead of grading the call again. Every hit and miss reads as before; on a few past calls the Brier score or the percent moves by its last digit, and the confidence tier can move with it, to match what that game card printed. No call itself changes.
- When part of a run fails but the rest can still publish (the schedule and injury facts refresh, the fuller set of projection inputs, the value rankings or the rookie projections), the footer now says which part, instead of the run looking complete. The MCP's live legend also says that a reading made at halftime is made on the second half kickoff.
- A team page's projected wins, playoffs, playoff chances and simulated season now say "Not built yet." instead of "Not published yet.": nothing produces those numbers today, so they were never on their way. The how to read and about pages now call what we are measured against the benchmark rather than the ceiling, since a player's ceiling means something else here, and a game's interval is called its range throughout. The bar note beneath a game's margin reads "9 in 10 land inside" at both widths.
Under the hood
- Gate 2 skips the board parity step, seven to nine minutes a run, when a run that passed it already tested the exact same board inputs: the pages, components, fixtures, baselines, lockfile and the step itself, byte for byte. A small script hashes those inputs into a key, a passing run records the key, and a later run with the same key reuses that verdict. This covers the release pull request into main, adding the browser label, and pushes that touch nothing the boards read. Any change to a board input runs the step in full, and the populated build and the row check never skip.
- Gate 1 and Gate 2 now check the semantics of every page they build, in seconds each. Gate 1 reads its empty-state build and Gate 2 reads the populated one, with rankings rows, game cards and player pages. The checks: a language on the page, one main landmark, one top heading, unique ids, alt text on every image, list items inside lists, header cells in every table, no skipped heading level, and a name on every link and button.
- The live Worker now checks every quarter hour whether sporacle.app serves the run the published pointer names, and records the answer. When the two differ for longer than a rebuild takes, 45 minutes, it reports the failure to a healthchecks.io check, so a site rebuild that never landed raises an alert instead of leaving only a note in a run log. A new public read, live.sporacle.app/served, answers the published run and the last check; it reads two stored files and fetches nothing. The alert needs one secret, SERVED_HEALTHCHECK_URL, set on the live Worker; docs/runbooks/SERVED-STATE.md says how. The check runs beside the live poller rather than ahead of it, so a slow site never delays a score update. Once it has reported a failure it keeps reporting it until the site serves the published run, even if a newer run is published meanwhile, so a new run cannot clear the alert while the site is still behind.
- The deploy's served state report reads the published run through that public route, because the deploy token cannot read the bucket directly and the report had named the run unknown on every deploy since late October. It still never fails a deploy.
- Four writes held back until the site could read them are on now that Release 2.0.8 put their readers on main: the reasons in each run's manifest, the missing history on a rookie's ranking row, the injury report's release time, and the complete graded call on every track record game. The injury report's contract version moves to 1.3.0 for its new optional field. The track record's golden check names the graded call as an added field, and its pinned record now carries one per game. The writer for canonical player names stays off.
- A failure in one of those parts no longer passes for an honest absence: the log carries the traceback and the run's notes name the error. The scheduled ingest's health ping logs each failed attempt, and a programming error in it now raises instead of reading as a ping that did not get through. A few error handlers that caught everything now catch only the failures they mean.
- Code nothing called is gone: an unused cold start prior, two unused derivation helpers, a poll interval the live loop never read, unused feature lists and a scoring default, a test only cache reset in the live Worker, and two unused site helpers.
- The kickoff freeze is one call in its own module: the card guard, the card freeze, the clubs that have kicked off, the player guard and the player freeze now run in order on one publish time clock, instead of as eight steps scattered through the weekly run. Tests pin that the one call returns exactly what the steps returned, so no published call moves.
- Comments that told the story of how code was reached (review rounds, fix rounds, plan task numbers) now say only what the code guarantees and why, in the pipeline and the Workers.