Finding
tests/validators-smoke.test.mjs holds a hand-maintained array of validator
filenames:
const READ_ONLY_VALIDATORS = [
'validate-metrics.mjs',
'validate-awards.mjs',
'validate-architectures.mjs',
'validate-architecture-assets.mjs',
'validate-button-contrast.mjs',
];
Nothing keeps that list in sync with scripts/validate-*.mjs, and the list is
far more load-bearing than a unit-test fixture normally is.
It is the only thing that gates data integrity on a pull request.
.github/workflows/ci.yml is the sole pull_request-triggered workflow. Its
steps are npm ci, npm run test:unit, npm run build:production — it never
invokes npm run validate:*. The four data validators run directly only in
deploy-gh-pages.yml (trigger: push to main) and import-architectures.yml
(trigger: schedule). Both are post-merge.
So the validators reach the PR gate exclusively via
validators-smoke.test.mjs, which executes each listed script against the real
repository data.
Verified at 00b44df (node v26.8.1)
Mutating data/awards.json to give the first entry an http:// announcement
URL and running node --test:
✖ validate-awards.mjs passes against current repo data
stderr: '\n1 error(s) in awards:\n [error] 2026/sncf: announcementUrl must be https\n'
The unit suite catches it — but only because validate-awards.mjs happens to
appear in that array. A validator added to scripts/ and forgotten in the
array runs on no pull request. Its first failure lands post-merge on the
push-to-main deploy, where it blocks deployment of an already-merged change,
and the author who introduced the break is no longer in the loop.
Why existing gates miss it
Recommendation
"Read-only" is detected from the script source rather than hard-coded, so the
guard does not go stale when a validator gains a write mode.
validate-architecture-assets.mjs has a --fix path and is therefore not
required to be listed, though it is listed today and that stays valid.
Verified: green on a clean checkout (4/4 pass, full suite 59/59), and dropping
a read-only scripts/validate-foo.mjs into the tree fails it with
actual: [ 'validate-foo.mjs' ]. Already prettier --check clean.
This is test-only — no production file changes.
Priority
- Impact: medium
- Effort: low
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5
Finding
tests/validators-smoke.test.mjsholds a hand-maintained array of validatorfilenames:
Nothing keeps that list in sync with
scripts/validate-*.mjs, and the list isfar more load-bearing than a unit-test fixture normally is.
It is the only thing that gates data integrity on a pull request.
.github/workflows/ci.ymlis the solepull_request-triggered workflow. Itssteps are
npm ci,npm run test:unit,npm run build:production— it neverinvokes
npm run validate:*. The four data validators run directly only indeploy-gh-pages.yml(trigger:pushtomain) andimport-architectures.yml(trigger:
schedule). Both are post-merge.So the validators reach the PR gate exclusively via
validators-smoke.test.mjs, which executes each listed script against the realrepository data.
Verified at
00b44df(node v26.8.1)Mutating
data/awards.jsonto give the first entry anhttp://announcementURL and running
node --test:The unit suite catches it — but only because
validate-awards.mjshappens toappear in that array. A validator added to
scripts/and forgotten in thearray runs on no pull request. Its first failure lands post-merge on the
push-to-main deploy, where it blocks deployment of an already-merged change,
and the author who introduced the break is no longer in the loop.
Why existing gates miss it
scripts/validate-foo.mjsleaves all 55 existing tests green.
docusaurus builddoes not model the test runner's inputs.tests/workflow-scripts.test.mjs, open) asserts thatnpm runtargets named in workflows and
package.jsonresolve to defined scripts andexisting files. It does not read
validators-smoke.test.mjsand does notmodel which validators actually execute on a pull request.
tests/ci-supply-chain.test.mjs, open) covers action pinning andtoken permissions, not step composition.
tests/tools/coverage-report.mjs, open) changes how coverage isreported for fixture-sandbox runs; it does not touch the smoke list.
tests/validators-smoke.test.mjs.Recommendation
tests/validator-smoke-coverage.test.mjs, which parses theREAD_ONLY_VALIDATORSliteral out oftests/validators-smoke.test.mjsand asserts, in both directions, that it matches the read-only
scripts/validate-*.mjsfiles on disk."Read-only" is detected from the script source rather than hard-coded, so the
guard does not go stale when a validator gains a write mode.
validate-architecture-assets.mjshas a--fixpath and is therefore notrequired to be listed, though it is listed today and that stays valid.
Verified: green on a clean checkout (4/4 pass, full suite 59/59), and dropping
a read-only
scripts/validate-foo.mjsinto the tree fails it withactual: [ 'validate-foo.mjs' ]. Alreadyprettier --checkclean.This is test-only — no production file changes.
Priority
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5