Repository navigation
[quality] data/kubecon-na-2026-end-user-talks.json has no integrity test, so a corrupt snapshot passes CI #1382
Description
Finding
data/kubecon-na-2026-end-user-talks.json is the only committed dataset under
data/ with no integrity gate of its own.
Every other dataset has one or both of:
- a
scripts/validate-*.mjswired into thevalidatejob in
.github/workflows/ci.yml(all 13validate:*npm scripts run there), or - a
tests/*-data.test.mjsthat asserts the committed file's own invariants
(tests/awards-data.test.mjs,tests/members-data.test.mjs,
tests/community-roster-data.test.mjs,tests/milestones-data.test.mjs,
tests/community-people-data.test.mjs).
The talks snapshot has neither. The two tests that read it are rendering
contracts, not data contracts:
tests/end-user-talk-schedule.test.mjsrenders
src/components/EndUserTalkSchedule/index.jsand compares the output to
expectations it derives from the same file. Both sides move together, so a
corrupt snapshot stays green.tests/collect-end-user-talks.test.mjsexercises
scripts/collect-end-user-talks.mjsagainst a synthetic Sessionize fixture.
It proves the collector is correct; it never looks at the committed output.
So the following all pass CI today, unchanged:
- duplicate
day.idvalues — the component emitsid={day.id}on each<h5>
andhref={'#' + day.id}in the nav, so a duplicate silently produces
duplicate DOM ids and jump links that land on the wrong day. The existing
test compares nav hrefs to day ids, and both are derived from the same
array, so it cannot see this. - duplicate
talk.idvalues across the file - a slot whose
start/endfall on a different calendar date than theday
that contains it endat or beforestartstartLabel/endLabelthat disagree withstart/end- days or slots out of chronological order, which the component renders in
array order - a
generatedAtin the future, or atalkCountthat disagrees with the
number of talks actually present
The file is machine-generated and carries "Do not hand-edit.", but it is
committed, reviewed and refreshed by hand (npm run collect:end-user-talks is
not run by any workflow), so a bad or partial snapshot reaches main through
the ordinary PR path.
Coverage evidence
This is not a line-coverage gap, and no coverage number will surface it:
- Unit:
npm run test:unit:coverage(node 26.10.0, this checkout at
f5586b0) reportssrc files | 100.00 | 100.00 | 9405/9405 lines | 2802/2802 regions. - End-to-end:
e2e-coverageartifact11783589900from run
38334322389
(report.txt, statuspassed, run id38334322389-1) reports
src/components/EndUserTalkSchedule/index.js | 100.00 | 100.00.
Both lanes execute every line of the component against the committed data, and
neither can tell a correct snapshot from a corrupt one. The gap is in asserted
behaviour.
Recommendation
Add tests/end-user-talks-data.test.mjs, following the
tests/awards-data.test.mjs pattern: load the committed JSON and assert the
invariants scripts/collect-end-user-talks.mjs guarantees by construction —
- top-level shape:
description,sourceUrl,apiUrl,category,
generatedAt,timezoneare non-empty strings;daysis a non-empty
array -
generatedAtparses and is not in the future -
talkCountequals the number of talks reachable through
days[].slots[].talks[] -
day.dateis aYYYY-MM-DDcalendar date;day.idandday.dateare
each unique across the file -
day.idis the collector's derivation ofday.label(lowercase, commas
dropped, spaces to hyphens), andday.labelis the UTC long-form
rendering ofday.date - days are in ascending date order, and each day's slots are in ascending
startorder - every slot
start/endis an event-localYYYY-MM-DDTHH:MM:SSstamp
beginning with its day'sdate, withendstrictly afterstart -
startLabel/endLabelmatch the collector's 12-hour rendering of
start/end -
(start, end)is unique within a day — the collector folds equal pairs
into one slot - every slot has at least one talk;
talk.title,talk.speakersand
talk.roomare non-empty strings;talk.idis unique across the file
Deliberately out of scope, to stay disjoint from #1375: nothing about
talk.url's scheme, host or shape. That file's link safety is that PR's
ground.
Priority
- Impact: medium
- Effort: low
Filed by quality agent (hold-gated mode)
Activity