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
data/community-people.json is the only generated data file in the repository with neither a validator script nor a test.
It is regenerated on a schedule by .github/workflows/refresh-community-people.yml (scripts/fetch-community-people.mjs) from data/community-roster.json plus live api.github.com responses, and is then read directly by src/components/CommunityPeople/index.js, which docs/community/end-user-community.md renders twice:
The component does peopleData.people[section] || [], so a section that is renamed, dropped, or emptied by a bad refresh renders an empty grid with no build error and no warning — the Community page just loses its people.
Other unguarded couplings between the generated file and the component:
image is rendered straight into <img src={image}>. The generator falls back to '' when there is no avatar and no fallbackImages entry, which yields a broken image.
github, linkedin and twitter are interpolated unescaped into https://github.com/${value}, https://www.linkedin.com/in/${value} and https://twitter.com/${value}. A handle containing /, ?, # or .. — these values come from data/community-roster.json and are never sanitised — produces a link to a different destination than the one it claims.
blog is normalised as blog.startsWith('http') ? blog : 'https://' + blog, so a non-URL value becomes a nonsense link.
key={person.name} requires names to be unique within a section.
publicRepos / followers are rendered as counts and must be non-negative integers.
By contrast metrics.json, awards.json and data/architectures/ each have a scripts/validate-*.mjs run in CI, and the remaining data/*.json files have contract tests open (#234, #238, #240-cluster, #252).
Recommendation
Add a test-only contract suite, tests/community-people-data.test.mjs, asserting the committed data/community-people.json against exactly the shape its consumers rely on:
fetchedAt is a parseable date; people is a plain object
the section keys equal the section keys of data/community-roster.json (the generator's own invariant)
every section="..." referenced by docs/**/*.md exists and is non-empty, discovered by scanning the docs rather than hard-coded
per person: name non-empty and unique within its section; image a non-empty https:// URL; bio/location/blog strings; role/linkedin/twitter/profileUpdatedAt string-or-null; publicRepos/followers non-negative integers; profileUpdatedAt parseable when present
github/linkedin/twitter match a conservative handle pattern, so they cannot change the destination of the URLs the component builds from them
blog is either empty or normalises to an http/https URL under the component's own rule
This needs no new production code and no workflow change — npm run test:unit (node --test) already picks up tests/*.test.mjs, and CI already runs it.
Coverage evidence
Unit:node --test --experimental-test-coverage, local clone of cncf/endusers at 00b44df after npm ci, 2026-09-17 — 55 tests pass, 0 fail. The coverage report lists only scripts/** and tests/helpers.mjs; no test loads data/community-people.json.
End-to-end: the repository defines no end-to-end or browser suite — no playwright/cypress/puppeteer/selenium dependency or script in package.json, and no such job in any of the six workflows in .github/workflows/ (grep at 00b44df). It also publishes no coverage artifact from any suite, tracked separately in [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186, so no cross-suite coverage merge was possible.
Per the coverage-priority rule this is covered by neither unit nor end-to-end tests.
Scope
This issue claims data/community-people.json and one new file, tests/community-people-data.test.mjs. It is disjoint from #213/#214 (which cover scripts/fetch-community-people.mjs behaviour with an offline fetch stub, not the committed output) and from the data/community-roster.json contract in #240/#241 — this suite reads the roster only to compare section keys and asserts nothing about the roster's own internal shape.
Priority
Impact: medium — a silent content regression on a published page, on an automated schedule
Effort: low — one test-only file, no new dependency, no workflow change
Finding
data/community-people.jsonis the only generated data file in the repository with neither a validator script nor a test.It is regenerated on a schedule by
.github/workflows/refresh-community-people.yml(scripts/fetch-community-people.mjs) fromdata/community-roster.jsonplus liveapi.github.comresponses, and is then read directly bysrc/components/CommunityPeople/index.js, whichdocs/community/end-user-community.mdrenders twice:The component does
peopleData.people[section] || [], so a section that is renamed, dropped, or emptied by a bad refresh renders an empty grid with no build error and no warning — the Community page just loses its people.Other unguarded couplings between the generated file and the component:
imageis rendered straight into<img src={image}>. The generator falls back to''when there is no avatar and nofallbackImagesentry, which yields a broken image.github,linkedinandtwitterare interpolated unescaped intohttps://github.com/${value},https://www.linkedin.com/in/${value}andhttps://twitter.com/${value}. A handle containing/,?,#or..— these values come fromdata/community-roster.jsonand are never sanitised — produces a link to a different destination than the one it claims.blogis normalised asblog.startsWith('http') ? blog : 'https://' + blog, so a non-URL value becomes a nonsense link.key={person.name}requires names to be unique within a section.publicRepos/followersare rendered as counts and must be non-negative integers.By contrast
metrics.json,awards.jsonanddata/architectures/each have ascripts/validate-*.mjsrun in CI, and the remainingdata/*.jsonfiles have contract tests open (#234, #238, #240-cluster, #252).Recommendation
Add a test-only contract suite,
tests/community-people-data.test.mjs, asserting the committeddata/community-people.jsonagainst exactly the shape its consumers rely on:fetchedAtis a parseable date;peopleis a plain objectdata/community-roster.json(the generator's own invariant)section="..."referenced bydocs/**/*.mdexists and is non-empty, discovered by scanning the docs rather than hard-codednamenon-empty and unique within its section;imagea non-emptyhttps://URL;bio/location/blogstrings;role/linkedin/twitter/profileUpdatedAtstring-or-null;publicRepos/followersnon-negative integers;profileUpdatedAtparseable when presentgithub/linkedin/twittermatch a conservative handle pattern, so they cannot change the destination of the URLs the component builds from themblogis either empty or normalises to anhttp/httpsURL under the component's own ruleThis needs no new production code and no workflow change —
npm run test:unit(node --test) already picks uptests/*.test.mjs, and CI already runs it.Coverage evidence
node --test --experimental-test-coverage, local clone ofcncf/endusersat00b44dfafternpm ci, 2026-09-17 — 55 tests pass, 0 fail. The coverage report lists onlyscripts/**andtests/helpers.mjs; no test loadsdata/community-people.json.playwright/cypress/puppeteer/seleniumdependency or script inpackage.json, and no such job in any of the six workflows in.github/workflows/(grep at00b44df). It also publishes no coverage artifact from any suite, tracked separately in [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186, so no cross-suite coverage merge was possible.Scope
This issue claims
data/community-people.jsonand one new file,tests/community-people-data.test.mjs. It is disjoint from #213/#214 (which coverscripts/fetch-community-people.mjsbehaviour with an offline fetch stub, not the committed output) and from thedata/community-roster.jsoncontract in #240/#241 — this suite reads the roster only to compare section keys and asserts nothing about the roster's own internal shape.Priority
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5