You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scripts/validate-awards.mjs is the only gate on data/awards.json, and it is
deliberately tolerant: it checks truthiness, an ^https:// prefix, and a year
floor. src/components/AwardsTimeline/index.js is stricter than that in several
places, so an awards file that passes npm run validate:awards can still render
a wrong or misleading /awards page.
No test asserts the data against the component. tests/validate-awards.test.mjs
exercises the validator against synthetic fixtures only; it never reads the
real data/awards.json, and nothing in tests/ reads src/components/AwardsTimeline/index.js.
Evidence
Verified on a clean clone at 00b44df, node v26.8.2, 2026-09-20.
Nine single-field mutations were applied to data/awards.json, one at a time. Eight of the nine pass npm run validate:awards:
mutation
validate:awards
what the page does
verifiedAgainst repointed to another host
PASS
misattributes its own audit source (see below)
verifiedAt set to a future date
PASS
banner claims an audit that has not happened
citation set to a number
PASS
renders 5 as the citation
event set to " "
PASS
renders a blank line
slug set to Acme_Corp
PASS
non-kebab React key
duplicate winner entry inserted
PASS
winner rendered twice, no warning
announcementUrl set to https://
PASS
new URL() throws; non-navigable anchor
two awardLabels for one award slug
PASS
reads as two different awards
out-of-range year (1999)
FAIL
already caught
The verifiedAgainst case is the sharpest. AwardsTimelinehard-codes the
visible link text:
The validator accepts any https:// URL for verifiedAgainst, so the href and
the label it is rendered under can drift apart and the page then misattributes
the provenance of its own winner history — exactly the claim the banner exists
to make.
Why existing gates miss it
npm run validate:awards is the tolerant gate described above.
docusaurus build / onBrokenLinks: 'throw' govern page routes, not JSON
field shapes or external hrefs.
One test-only PR closes this. No production change is required — every
assertion below is green against main as it stands today.
Add tests/awards-data.test.mjs asserting the real data/awards.json
against the contract AwardsTimeline depends on: top-level keys present; verifiedAt a non-future YYYY-MM-DD; verifiedAgainst host+path equal
to the label the component hard-codes; every rendered text field a
non-empty string; kebab-case slug and integer year; newest-first
ordering; no duplicate year/slug; every present link a resolvable https URL with a host; every card has a primary link; one display label
per award slug; and a reverse guard that fails if WinnerCard starts
destructuring a field the contract does not cover.
Priority
Impact: medium — the /awards page is the site's public record of CNCF End
User award winners, and the failure mode is silent misattribution rather than
a broken build.
Effort: low — one new test file, no dependencies, no production change.
Finding
scripts/validate-awards.mjsis the only gate ondata/awards.json, and it isdeliberately tolerant: it checks truthiness, an
^https://prefix, and a yearfloor.
src/components/AwardsTimeline/index.jsis stricter than that in severalplaces, so an awards file that passes
npm run validate:awardscan still rendera wrong or misleading
/awardspage.No test asserts the data against the component.
tests/validate-awards.test.mjsexercises the validator against synthetic fixtures only; it never reads the
real
data/awards.json, and nothing intests/readssrc/components/AwardsTimeline/index.js.Evidence
Verified on a clean clone at
00b44df, node v26.8.2, 2026-09-20.Nine single-field mutations were applied to
data/awards.json, one at a time.Eight of the nine pass
npm run validate:awards:validate:awardsverifiedAgainstrepointed to another hostverifiedAtset to a future datecitationset to a number5as the citationeventset to" "slugset toAcme_CorpannouncementUrlset tohttps://new URL()throws; non-navigable anchorawardLabels for oneawardslugyear(1999)The
verifiedAgainstcase is the sharpest.AwardsTimelinehard-codes thevisible link text:
The validator accepts any
https://URL forverifiedAgainst, so the href andthe label it is rendered under can drift apart and the page then misattributes
the provenance of its own winner history — exactly the claim the banner exists
to make.
Why existing gates miss it
npm run validate:awardsis the tolerant gate described above.docusaurus build/onBrokenLinks: 'throw'govern page routes, not JSONfield shapes or external hrefs.
node --test, so no test can import thecomponent (see [quality] filterArchitectures is untested, and no test can import any src/ React module #228); the contract has to be asserted against the component
source plus the real data.
AwardsTimelineor readsdata/awards.jsontop-level fields. [quality] test: cover data/members.json contract and awards.json slug parity (tests/members-data.test.mjs) #253 (tests/members-data.test.mjs)touches
awards.jsononly formembers.json↔ award slug parity — itasserts nothing about
verifiedAt,verifiedAgainst, ordering, duplicates,URL resolvability, or label consistency, and does not read any component.
fix: resolve award logo paths before checking /img/awards/ containment (scripts/validate-awards.mjs) #329 claims award logo path resolution, which this issue deliberately
leaves alone. test: cover the data/metrics.json render contract (tests/metrics-data.test.mjs) #287 is the same species of contract for
metrics.json↔MetricsDashboard; test: assert the data/architectures catalog ↔ records ↔ docs contract (tests/architecture-catalog-contract.test.mjs) #260 is the catalog ↔ records ↔ docs contract. [quality] test: cover src/components/ArchitectureFilters filterArchitectures and add a JSX import path (tests/tools/jsx-hooks.mjs + tests/helpers-jsx.mjs + tests/architecture-filters.test.mjs) #229 coversfilterArchitectures, test: cover useFocusTrap scroll lock, tab cycling and focus restore #268useFocusTrap.Coverage evidence
node --test --experimental-test-coverage, node v26.8.2, local runat
00b44df, 2026-09-20 — 55 pass. No unit coverage of this contract.artifact (
package.jsonhas no browser/e2e runner;check:linksdepends on aMakefile target that is absent — see fix: rewire check:links off missing Makefile and drop orphan typecheck script #291). End-to-end coverage is therefore
indeterminate, not absent, and no end-to-end claim is made here. That
reporting limitation is already tracked by [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186 and is not re-filed.
Recommendation
One test-only PR closes this. No production change is required — every
assertion below is green against
mainas it stands today.tests/awards-data.test.mjsasserting the realdata/awards.jsonagainst the contract
AwardsTimelinedepends on: top-level keys present;verifiedAta non-futureYYYY-MM-DD;verifiedAgainsthost+path equalto the label the component hard-codes; every rendered text field a
non-empty string; kebab-case
slugand integeryear; newest-firstordering; no duplicate
year/slug; every present link a resolvablehttpsURL with a host; every card has a primary link; one display labelper
awardslug; and a reverse guard that fails ifWinnerCardstartsdestructuring a field the contract does not cover.
Priority
/awardspage is the site's public record of CNCF EndUser award winners, and the failure mode is silent misattribution rather than
a broken build.
— hive: agent=quality backend=copilot model=claude-opus-5