Write down that a source can be empty without being broken
The two preceding changes make sentences in docs/ false. INGESTION.md described `canParse` as a structural check without saying that "structural" excludes the presence of a row — which is exactly the drift that let this bug in — and described the Stage 1 gate as rejecting every empty parse. SOURCES.md § 11 left a reader to conclude that an empty Infinity Nikki lane means the source has died. AGENTS.md gets the rule in both places somebody would look: § Working on parsers, next to the bullet about a source resting on one row, and § Fandom where the fourth template is described. The general lesson is worth more than the incident. Any page that can list nothing can produce this, so the guidance is about what identifies a page versus what happens to be on it — not about this wiki. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4b2b3d0958
commit
4d45635b0e
+28
-1
@@ -202,6 +202,7 @@ export interface SourceParser {
|
||||
id: string; // "game8"
|
||||
label: string; // "Game8"
|
||||
canParse(html: string): boolean; // structural sanity check
|
||||
statesNoEvents?(html: string): boolean; // the page says it lists none
|
||||
parse(html: string, ctx: ParseContext): GachaEvent[];
|
||||
}
|
||||
```
|
||||
@@ -216,6 +217,24 @@ in attribute quote style (`class="a-table"` on Genshin, `class='a-table'` on NTE
|
||||
the kind of variation a naive check gets wrong. Every regex in `html.ts` is attribute-agnostic for
|
||||
the same reason.
|
||||
|
||||
**"Structural" means identity, never the presence of a row** — and that half was learned the
|
||||
expensive way. `isInfinityNikkiEventPage` recognised its page by finding a *populated*
|
||||
`Current`/`Upcoming` table, so when the wiki emptied both between versions (2026-09-03, § below) the
|
||||
tripwire fired on a page whose markup had not changed by a byte, reported
|
||||
`the source has likely been redesigned`, and reached the `broken` tier in a day and a half. A check
|
||||
that reads data cannot tell a rewrite from a quiet week; a check that reads headings, ids or column
|
||||
names can, because those survive an empty table. Ask what identifies the page, not what is on it.
|
||||
|
||||
`statesNoEvents` is the other half of that lesson, and it is optional because most pages say nothing
|
||||
either way. It answers a question `canParse` and a row count together cannot: **is this source
|
||||
broken, or is this game simply between patches?** Both look identical from a count of zero, so the
|
||||
signal has to be the page's own words — Infinity Nikki's wiki prints "There are no Events in this
|
||||
category" under both headings when it is listing nothing. A parser that implements it lets the
|
||||
refresh runner store an empty snapshot as a real answer (§ Stage 1); one that does not keeps the
|
||||
strict gate, which is the right default. Never infer it from an empty table: a redesign that broke
|
||||
every selector yields an empty table too, and storing *that* is the silently emptied calendar the
|
||||
gate exists to prevent.
|
||||
|
||||
### The adapter registry
|
||||
|
||||
```ts
|
||||
@@ -376,7 +395,15 @@ conduct for why that ordering is load-bearing.
|
||||
injection, so the whole runner is tested offline against a fake fetch. A fetched body is *rejected*
|
||||
— the previous snapshot survives — when it fails `canParse`, throws, or yields zero events; storing
|
||||
an empty parse would make the feed build prefer it over the fixture and silently empty a game's
|
||||
calendar. One source down is a warning and exit 0; every source failing is exit 1, so CI never
|
||||
calendar.
|
||||
|
||||
The one exception is a page that states its own emptiness: when a parser's `statesNoEvents` says the
|
||||
document itself reports listing nothing, the empty parse is stored as that source's real answer and
|
||||
the cycle counts as confirmed rather than failed. Without it a game between versions fails every
|
||||
cycle until its next patch ships — three of them reach the `broken` tier and fail the workflow, over
|
||||
a lane that is correctly empty, while the snapshot it is holding goes on ageing. The distinction is
|
||||
the page's statement and never a row count, so a redesign still rejects, and the run says which of
|
||||
the two it saw: `0 events — the page states it currently lists none`. One source down is a warning and exit 0; every source failing is exit 1, so CI never
|
||||
commits a cycle that learned nothing.
|
||||
|
||||
## Stage 2 — parse
|
||||
|
||||
@@ -430,6 +430,22 @@ Two shapes the parser handles, both of which would otherwise lose or corrupt a r
|
||||
Asking the wiki's editors to state the zone on the Duration column would upgrade this source to
|
||||
exact instants, and remains the cheapest improvement available to it.
|
||||
|
||||
**Update, 2026-09-03: this page goes empty between versions, and the source was reading that as a
|
||||
redesign.** With 2.7's events ended and 2.8 not yet listed, the wiki replaced both the `Current
|
||||
Events` and `Upcoming Events` tables with `There are no Events in this category`, leaving everything
|
||||
else — including twenty `Past Events` tables of identical shape — untouched. `canParse` identified
|
||||
the page by finding a *populated* table, so it threw `the source has likely been redesigned` at a
|
||||
page nobody had touched; four cycles of that reached the `broken` tier and the calendar went on
|
||||
holding a snapshot whose every row had expired on 27 August.
|
||||
|
||||
The fix was in the pipeline, not in this assessment: `canParse` now identifies the page by its
|
||||
section headings, and `statesNoEvents` lets a parser report the page's own declaration of emptiness
|
||||
so the runner stores it as an answer instead of a failure (`docs/INGESTION.md` § The parser interface
|
||||
and § Stage 1). **So an empty Infinity Nikki lane is now the expected state between patches, not a
|
||||
symptom** — the thing to check before suspecting this source is whether the wiki is listing anything
|
||||
at all. It refills on its own when the next version is posted. The verdict on the source is
|
||||
unchanged and it stays built.
|
||||
|
||||
## 12. Thirteen Game8 hubs, swept on 2026-08-19 — two are worth building, eleven are not
|
||||
|
||||
Not a P1 game between them: this section answers a direct request to check a list of Game8 game
|
||||
|
||||
Reference in New Issue
Block a user