Why it is not optional bookkeeping
JSON-LD is the one output nobody looks at. A page renders identically whether its structured data is correct or ruined, so a regression here is invisible in review, invisible in the dev server, and invisible to the person whose site is publishing it.
The evidence is in SPEC-130's own history. Every defect it found was found by running extractSeo, never by reading a transform: MusicPlaylist on podcasts (BUG-013), the BreadcrumbList that renders and is never harvested, NonProfit — a type schema.org does not have — sitting inside a validated enum, seven entities asserting a type and describing nothing. And four Lumina rules pointing at attributes that moved years ago (BUG-015) survived every review in between.
This milestone rewrites that channel across 30 runes in 10 packages. The baseline is what turns each step from a leap into a diff.
Scope
The existing coverage is not this. packages/runes/test/seo.test.ts and the per-plugin seo.test.ts files assert a handful of runes — accordion, breadcrumb, playlist — with hand-written expectations, and several runes this milestone changes have no JSON-LD test at all.
What is wanted is per-rune fixtures run through extractSeo and committed as snapshots, covering every rune that emits a typeof today: all of Group A (7), Group B (14) and Group C (9).
Acceptance Criteria
- A committed JSON-LD snapshot covers all 30 emitting runes, generated by running
extractSeo over a per-rune fixture - The snapshot is regenerable by a documented command, so a deliberate change is a reviewable diff rather than a hand edit
- Comparison normalises object key order and preserves array order —
itemListElement, step, track and recipeInstructions are ordered sequences and a reordering is a real defect - Runes with no JSON-LD test today are covered — the gap is named in the item, not discovered during migration
- The rendered RDFa is captured alongside the JSON-LD for each fixture, so WORK-563's two-point invariant has something to compare against
- Nothing in the snapshot is edited to look correct — BUG-013's mistyped podcasts and
testimonial's jobTitle: ", CTO at Acme" comma defect are recorded as they are - The existing hand-written
seo.test.ts expectations are either folded in or left in place with a note saying which is authoritative
Approach
Snapshot the wrong output too. The instinct is to fix the obvious defects while writing fixtures for them. Don't — a baseline that has been tidied cannot prove that a later change was intentional. playlist type="podcast" should record MusicPlaylist today and change in WORK-569; that diff is the evidence the fix landed.
Fixtures should be minimal but real: enough content to populate every declared property, since SPEC-130's D6 turns on single- versus multi-item collections behaving differently (appendToProperty stores the first value as a scalar and only promotes on the second). Include at least one single-item and one multi-item case for every rune that emits a list, or D6's change will land as an unreviewable diff.
Rune-level fixtures are the cheap half. WORK-563 adds the page-level assertion through runPipeline, which is what catches the postProcess class — they are the same assertion at two granularities and the page-level one belongs with the harvest move that makes it meaningful.
References
- SPEC-130 — "Before anything moves: the baseline", and D6
- BUG-013 — recorded as-is, fixed in WORK-569
packages/runes/src/seo.ts:188 — extractSeopackages/runes/test/seo.test.ts — the coverage this replaces