fix(ci): run the benchmark-cell validator in a workflow that exists - #69
Merged
Merged
Conversation
`scripts/build-data.ts` has a real validator and consults it in exactly one place -- `if (validateOnly) process.exit(validationErrors > 0 ? 1 : 0)` -- which only `bun run validate` reaches. No workflow ran it. CI ran `build`, `check` and `test`; Cloudflare runs `bun run build`. On the plain build path a malformed cell is warned about, marked "excluded:invalid", dropped from the payload and the process exits 0, so a corrupt v1.6.0 cell would vanish from the dashboard with every check green. `Sync Benchmarks` was the other half: two `echo` statements, no checkout, no network call, unconditionally green on all 7 runs since 2026-06-28 -- while v1.5.9, v1.5.10 and v1.5.11 shipped without ever reaching `results/`. It reads as coverage in the checks list and is none. - ci.yml gains a `Validate benchmark cells` step (`bun run validate`) ahead of Build, so a bad cell is red before it can reach main. `build` stays lenient on purpose: one corrupt cell must not take the whole deploy down, which is exactly why the gate has to be a separate check rather than a stricter build. - sync-benchmarks.yml now checks out, asserts that the cell the dispatch pointer describes is actually committed (retrying, since push and dispatch are not atomic) and runs the same validator over the tree. A publish that never landed now fails the job instead of logging a line and going green. - build-data --validate-only counts and reports the cells it opened, and exits 2 if that count is zero -- a gate that inspected nothing must not report success. It also scans DEACTIVATED_VERSIONS, which the build skips: hiding a version from the dashboard is a presentation decision, not a licence for its committed data to rot. 6 cells were reachable before, 9 are now. - `validateEnv` was written, exported and called by nothing. It is now wired into loadCell as an error, so a cell whose env.json disagrees with its own path -- the mislabelled-arch corruption scrubbed by hand in d3424f4 -- is rejected. These are errors that do not null the cell: the site keeps rendering what it always rendered, the gate goes red. - Six tests cover the gate itself, including that the lenient build still exits 0 on the same malformed cell, and that an empty tree fails instead of passing. Docs corrected where they described the old behaviour: results/README.md still claimed sync-benchmarks pings a Cloudflare Pages deploy hook, README.md called sync-benchmarks the only workflow, and CONTRIBUTING.md called validate "the gate the benchmark publisher relies on" when nothing invoked it. Closes #67
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
goceleris-docs | 09e42cb | Commit Preview URL Branch Preview URL |
Sep 15 2026, 01:35 AM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #67.
What was wrong
scripts/build-data.tsconsults its validation count in exactly one place:validateOnlycomes only from--validate-only, i.e.bun run validate, and no workflow ran it. CI ranbuild,check,test; Cloudflare Workers Builds runsbun run build. On the plain build path a malformed cell iswarn()ed, marked"excluded:invalid", dropped from the payload, and the process exits 0.A second dead branch:
validateEnv()insrc/lib/results/validate.tswas written, exported, and called by nothing —load.tsimported onlyvalidateSummaryandvalidateTimeseriesDoc. That is the check for a cell whoseenv.jsondisagrees with its own path, which is the corruption that had to be scrubbed by hand in d3424f4.And
Sync Benchmarkswas twoechostatements: no checkout, no network call,contents: read, nothing to fail. 36 runs in its life; the only 2 failures are from June, before f21beba/cf01269 hollowed it out. All 7 runs since 2026-06-28 are green, unconditionally — while v1.5.9, v1.5.10 and v1.5.11 shipped and none of them reachedresults/, which still ends at v1.5.8.What the validator checks
Entry point
loadCell()(src/lib/results/load.ts), which returns{ cell, errors, warnings };build-data.tsincrementsvalidationErrorsfor every entry inerrors. Rejections:summary.jsonmissingload.tssummary.jsonunparseableload.tsschema_versionnot5.xvalidateSummarybenchmarksnot an arrayvalidateSummaryhost_arch_pairnotos/archvalidateSummaryenv.schema_versionnotenv/1validateEnv(newly wired)env.archnot in{x86_64, arm64}validateEnv(newly wired)env.version/date/archdisagree with the pathvalidateEnv(newly wired)scenarios[]validateTimeseriesDocenv.jsonabsent or unparseableload.tsvalidateEnv's findings are errors that do not null the cell: the site keeps rendering exactly what it rendered before, and the gate goes red. Nothing about the published dashboard changes in this PR.Where it is wired, and why there
ci.yml—Validate benchmark cells, beforeBuild. Pull request and push tomain. This is the pre-merge gate, and it is also the path that covers the publisher:mage Publishpushes with a PAT (DOCS_DISPATCH_TOKEN), notGITHUB_TOKEN, so its commit tomaindoes trigger workflows. It runs beforeBuildbecause it costs under a second and the gate should be the first thing that speaks.bun run buildis deliberately left lenient. Making the build itself fail would mean one corrupt cell takes down the entire site deploy. That leniency is the whole reason the red check has to be a separate step rather than a stricter build.sync-benchmarks.yml— made real. It now checks outmain, asserts that the cell the dispatch pointer describes is actually committed, and runs the samebun run validateover the tree. This is the only place that can catch the second failure in #67 — a release that dispatches a publish whose data never landed. It is post-hoc, not a gate (the publisher pushes straight tomain), and the workflow says so in its own header.The pointer assertion retries for 75s across 5 attempts, re-fetching
results/, because push and dispatch are not atomic and a gate that invents its own failures is worse than none.Not deleted. #67 offers deletion as the alternative if the workflow cannot be made real. It can be, and the thing it can do is the thing nothing else does: every other check reasons about the tree in the repo, and only this one is told what the publisher believed it published. Deleting it would leave that comparison unmade.
Proof: it fails on a malformed cell
Throwaway branch
test/malformed-cell-proof(PR #68, closed and deleted), two injections, each asserted in place before committing:results/v1.5.8/20260829/x86_64/env.json—archx86_64→arm64results/v0.0.1/20260101/x86_64/summary.json—schema_version: "4.1",host_arch_pair: "linux-amd64",benchmarks: {}CI run 34917866394, job
build— failed in 17s at the validate step:Negative control, same tree, locally — the defect being fixed, in one line:
A second control confirms injection (a) is genuinely new coverage and not something the old code already caught: with
src/lib/results/load.tsreverted and everything else in place, the mislabelled cell validates clean —1 cell(s) examined in 1 version(s), 0 validation error(s), exit 0.Proof: it passes on the real data, and how much it examined
This PR's own CI run, 34917943651, job
buildgreen in 36s:9 cells examined — measured, printed by the run itself, not assumed. That is every cell committed under
results/:Six of those nine are what the site build reads; the other three are in
DEACTIVATED_VERSIONS, which--validate-onlynow scans anyway — hiding a version from the dashboard is a presentation decision, not a licence for its committed data to rot.bun test: 22 pass, 0 fail, 80expect()calls.Why the cell count is now printed at all
Because "the gate ran" has to be a number.
--validate-onlyused to print only versions, errors and warnings — a run that walked an empty tree printed0 validation error(s)and exited 0, indistinguishable from a run that checked everything. It now reportsN cell(s) examinedand exits 2 if N is 0. A gate that inspected nothing must not report success; that is the same defect class as this issue, one level up.What is not proven here
sync-benchmarks.yml's new job cannot be triggered before it is onmain—workflow_dispatchonly offers workflows from the default branch, and firing a realbenchmark-publisheddispatch would mean running a benchmark. What is verified:bun run validatestep it runs is byte-identical to the one proven red and green above;run-1layout (pass), arun-2-rated/subdir (pass), a pointer to a cell that was never published — literally the v1.5.9/.10/.11 case — (fail, exit 1, with::error::publish dispatched for … but no summary.json is committed under …), and a pointer missing itsdate(fail, exit 1);actionlintis clean on both workflows.Suggested acceptance step after merge: run
Sync Benchmarksmanually withversion=v1.5.8 date=20260829 arch=x86_64(expect green) and then withversion=v1.5.11 date=20260907 arch=x86_64(expect red — that cell does not exist, and never did).Out of scope, spotted in passing
README.md's Quickstart still says the committed tree "holds real cells for v1.5.5 and v1.5.6". It holds v1.5.5–v1.5.8, and v1.5.5–v1.5.7 are deactivated sobun run devshows v1.5.8 only. Not touched here to keep this PR to one topic.