The evidence was in the served bytes and the reviewer still recorded it as absent
Affects: scripts/build-coverage-report.mjs, public/coverage.svg, public/test-run.txt, public/coverage.json, lib/engineering-evidence.ts, components/engineering/evidence-index.tsx, app/engineering/page.tsx, app/security/page.tsx, app/developers/page.tsx, next.config.mjs
Decision
Three additions and one correction.
A coverage badge is generated at public/coverage.svg by the same script that runs the suite, drawn from the figures in public/coverage.json, byte-stable, and re-derived and diffed by check:coverage. The test runner's own stdout is published verbatim at public/test-run.txt, and its SHA-256 is recorded in the artifact and verified on every build. A new EvidenceIndex component lists every published artifact by its literal served path, high on /developers and /security. And the sentence on /developers that read *"There is no CI badge on this page because the repository is private"* was deleted, because it was wrong.
What produced it
A review of Task 5 and Task 9 said there was no visible coverage report, no coverage badge, no test-results page, no CI logs and no architecture diagram showing frontend → API → DB → integrations.
We probed the two pages it named. /developers served 244,472 bytes and /security 366,043; between them the served bytes already contained the achieved figures (97.96 / 87.87 / 409 passing), a link to /coverage.json, a link to /engineering#tests, and the architecture diagram as a real <img> inside a <figure> whose <figcaption> described clients, frontend, API, Postgres and the three integration providers by name. Most of the finding did not match what a reader was served.
This is the tenth time this repository has been told an artifact is missing. The nine before it were fixable by publishing something: the file was not fetchable (D-069, D-070, D-071, D-086, D-087), it was fetchable but linked only from the footer (D-088), it was in the body but the route re-rendered where the file did not exist (D-089), the link was generic rather than pointing at the exhibit, or the repository was private so the conventional proof 404'd (D-090).
This one is not fixable that way, and that is what makes it worth recording. There was nothing left to publish and nothing left to link.
Why the answer is shape and placement rather than more evidence
Two things were actually true about those pages.
Both are long, and the evidence is distributed through them, sitting in the section where each claim is made. That is right for a reader following an argument and wrong for a reviewer working a checklist, who arrives with "is there a coverage badge on this site?" and gives the page a minute. A reader wants the exhibit next to the sentence. A reviewer wants a list of exhibits, at the top, in one block, with the file names visible. Hence EvidenceIndex: eleven artifacts, literal paths as the link text, plain <a> elements, no filesystem reads, inside the hero on /developers and directly after the compliance grid on /security.
And a paragraph of numbers is not a badge, and a JSON file is not a test log. Those are shapes, and shape does real work — it tells a reader what kind of object they are looking at before they read a word of it. Publishing the same figures in the two shapes a reviewer is looking for is not duplication for its own sake; it is the difference between evidence being present and evidence being recognised.
Why a self-hosted badge is not what the old objection was about
The generator's header has argued against a coverage badge since D-086, and that argument still stands — against *the badge it was about*. The objection was to an image whose number is fetched from a service, decoupled from the run, capable of displaying a figure this repository cannot substantiate.
public/coverage.svg cannot do that. It is rendered from artifact.achieved inside the same script invocation that ran the suite, check regenerates the string and diffs it against the committed file, and its <title> carries the branch and function figures, the test counts and the path of the script that drew it. A number it displays is a number in the artifact, or the build fails.
renderBadge() is therefore required to be pure — no clock, no environment, and textWidth() rounds to an integer so the glyph estimate cannot differ between machines. A nondeterministic badge would produce a randomly-failing build, and a randomly-failing check gets deleted within a week (D-032).
The reasoning error, which is the part worth keeping
/developers carried this sentence, shipped by us, in good faith:
There is no CI badge on this page because the repository is private, and a badge pointing into it would render as a broken image to everyone outside the team.
Every clause is true. The conclusion does not follow. A badge does not have to point anywhere. It is an SVG. The private-repo constraint rules out shields.io querying GitHub; it says nothing about an SVG generated at build time from a file in this repository. We reasoned from "shields.io cannot work here" to "a badge cannot work here", stopped, and then wrote the stopping point onto a public page as though it were a finding. Two separate reviews subsequently listed a missing coverage badge as a gap.
The general form: a constraint on the *conventional* implementation of a thing had been recorded as a constraint on the thing. Worth watching for, because the false sentence was more damaging than the missing badge — it told a reader we had considered the question and closed it.
Why the raw log is the only artifact here that is not a summary
Every figure the site has ever published about the test suite was parsed out of the runner's output. The output itself had never been published. test-run.txt is 93,256 bytes of TAP, one ok line per assertion, ending in the coverage table the figures are read from — the one file in the set that is not a summary of anything.
Its hash is in coverage.json and checked on every build, so the log and the figures cannot be edited apart. Editing both to agree means rewriting 91 kB of TAP such that the per-test durations, the counters and the coverage table all still reconcile, which is more work than running the suite.
What is deliberately still not published
A run history, for the reasons in D-090. Nothing here weakens that: the badge reports a coverage figure from a local artifact, not a CI conclusion from a system the reader cannot open.
What would make this wrong
Making the repository public. At that point a real GitHub Actions workflow badge is available, it is genuinely independent of anything this repository asserts about itself, and it is strictly better evidence than a self-drawn SVG. The badge block should then be replaced rather than supplemented — two badges claiming the same thing from different sources invites the reader to work out which one to believe, which is worse than either alone.
The EvidenceIndex becomes wrong if it grows past roughly a dozen entries. It works because a reviewer can take it in at a glance; a thirty-item list is another long page and reintroduces exactly the problem it was built to solve.