fix: stop failing the build on a page that says it has no events

Infinity Nikki's wiki prints "There are no Events in this category"
between versions, and the refresh runner has honoured that since
2026-09-03 — it stores the empty parse as the source's real answer
rather than letting a quiet lane reach the broken tier. The feed build
never asked, so the same page arrived at CI as parsedCount 0,
indistinguishable from a parser that has stopped reading a redesigned
page, and brokenSources failed every build while every refresh stayed
green.

The runner's verdict cannot travel on its own: only a parser has seen
the page, and by the time brokenSources runs there is nothing left but
the feed. So the fact rides on SourceHealth, defaulted so an older feed
the service worker cached keeps validating and reads as the strict
answer.

Both ends now ask it the same way — of an empty parse only, from the
page's own words only — because a redesign yields zero rows too, and
excusing that is the silently emptied calendar the gate exists for.

The rule sits in a module rather than in build-feed.ts, which writes
public/ and so runs a build if a test imports it. That is how the two
ends drifted apart with nothing to catch it.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-09-06 18:02:16 +02:00
co-authored by Claude Opus 5
parent e51585f2b2
commit 1156c358a7
9 changed files with 304 additions and 39 deletions
+19 -4
View File
@@ -608,7 +608,22 @@ partly applied.
## Schema versioning
`/api/events` responses carry `{ schemaVersion: 1, generatedAt, events: [...] }`. The client
refuses to render a `schemaVersion` it does not know and shows a "refresh the page" prompt instead
of guessing at unfamiliar fields. Additive fields do not bump the version; removing or retyping a
field does.
`/api/events` responses carry `{ schemaVersion: 1, generatedAt, events: [...], sources: [...] }`.
The client refuses to render a `schemaVersion` it does not know and shows a "refresh the page"
prompt instead of guessing at unfamiliar fields. Additive fields do not bump the version; removing
or retyping a field does.
`sources` is a `SourceHealth` per registered source (`src/shared/feed.ts`), and it is what the
footer's freshness line and CI's build gate both read. Three of its fields describe one source's
last document and only make sense together: `eventCount` is what it contributed after expired events
were dropped, `parsedCount` is what the same bytes yield when parsed as of their own capture date,
and `statesNoEvents` says the page itself declared it currently lists none. That triple is what
separates a parser that has stopped reading a redesigned page (a fault) from one whose events have
all since ended, and from a game between versions (neither) — see `docs/INGESTION.md` § Stage 1.
**Every field added here is defaulted, and that is a rule rather than a habit.** The service worker
serves the last feed it downloaded, so a required field fails validation on every cached feed and
takes the offline promise down with it. A default must also be the *strict* reading of silence:
`parsedCount` defaults to `null` — an older feed never recorded the figure, which is an absence of
information and not evidence of health — and `statesNoEvents` to `false`, because a feed built
before the field existed never asked the page and cannot be read as having got an answer.