Rationale
ADR 0141 put the UI on the public site as a snapshot: majordomus web report ui-snapshot
reads the UI once at the commit being published and writes it as files, a job of the pages
workflow builds it, and the deploy job places it at the UI's mount. The ADR said what it left
out: no public probe of the mount after the deploy.
An audit on 2026-10-12 found what that omission amounted to. Every check of the publication
read something else. scripts/pages verify read /build.json, which names the site's commit
and the composed surfaces and not the snapshot. verify-docs and verify-rustdoc read their
own surfaces. scripts/ci/pages-check, the pages-live gate that runs at every finish and
on a thirty-minute schedule, asked five questions and none about the UI. And the fact that a
publication holds the snapshot at all was written in one place, a step of
.github/workflows/pages.yml, so the other deploy path did not know it: scripts/site-deploy
run by a person empties the published branch and copies site/public, which the site build
never puts the snapshot into, and so published a site whose UI mount answered 404 with every
gate green.
Three failures followed from one cause, a surface that exists only where its producer ran and that nothing downstream was held to:
- Missing. A deploy that did not run the workflow's step drops the mount.
- Older. A snapshot left in
site/publicfrom an earlier build, or a CDN still answering the previous files, serves a UI read at another commit beside pages that name the new one. The UI is the product's first screen; a reader has no way to see that it is last week's. - Short. The producer is bounded on purpose and records what it left out and what stopped it. A tree half copied, or a record that does not say what ended the reading, looks the same as a whole one unless the pages the record lists are asked for.
A relation checked at one stage and owned by nobody at the next is this repository's most
repeated defect (project.the-crate-reference-is-published is the same finding about the
crate's reference). The stage after the producer is owned now.
Required behaviour
- Declared once.
.ai/repo/ci/pages.yamlnames, underdeploy.snapshots, each surface a publication holds that the site build does not compose: the served surface it is a snapshot of, where its producer writes it, the producer, and the attribute its pages state their commit with. The mount is not written there; it is the topology's mount of that surface (docs/generated/web.json), because a publication holds the snapshot where a process serves the original.scripts/pages snapshotsis the one reading of the entry, and the workflow, the deploy, the probe and the gate each go through it. - Read at the commit that is published. The snapshot a deploy publishes names, in its
surface.json, the commit the site's pages name. The trunk's head, within the deploy window, is what the site is held to (scripts/ci/pages-check, sections 1 and 3); the UI is held to the site's commit and therefore to the same window, never to a looser one of its own. - No deploy path without it.
scripts/site-deployrefuses a site whose tree lacks a declared snapshot at its mount, carries one read at another commit thanHEAD, or carries a declaration that names another mount than the one it stands at. The refusal names the producer and the placement.--release-metadatais not a deploy of the site — it adds two files to what the branch already serves — and is not asked. - Verified where a reader meets it.
scripts/pages verify-uiasks the public site: the snapshot'ssurface.jsonanswers, names the commit and the mount it is served at; the landing page answers and its banner states the same commit;snapshot.jsonanswers, names the same commit, and says what ended the reading and which pages were left out; and every page that record lists answers 200. The pages asked are bounded by--max-pages, spread evenly over the list, and the verdict states how many were asked, how many were not, how many the snapshot itself left out and what stopped it. - Cut short is stated, never hidden. A snapshot that stopped at its limit or its budget is a pass that says so. One whose record does not say what ended the reading, or does not list what it left out, is a failure: "every page" and "the pages there was time for" must be distinguishable from the public bytes alone.
- A gate, not a note. The pages workflow runs the probe after a deploy, once the site
serves the commit, and the step is not
continue-on-error.scripts/ci/pages-checkasks it of the published commit as section 2c; a UI that is absent, another commit's or not whole is exit10, and one that could not be judged is exit12with the half named in the verdict line, never a pass. - The first screen is decided. The figures the UI's first screen leads with are what a
visitor reads the product by, and the process that reads a snapshot is no server: it holds
no peer board and runs no mesh. So the screen leads only with what that process can
decide. Its health figure is
health.report'srepositoryverdict, the worst of the dimensions that are not the serving process's (PROCESS_DIMENSIONS: the server, its board, its mesh runtime), and where no mesh runtime is active the mesh figure says so and counts no machines. A first screen that shows the unknown badge or a source that did not answer in one of those figures is a finding of the probe, of the snapshot's own case, and therefore of the deployment.
Failure behaviour
scripts/pages snapshotsexits12when a declared surface has no mount in the topology or an entry does not name its artifact, producer and marker.scripts/site-deployexits10withREFUSEfor each case of item 3, and for a model that is present and cannot be read; nothing is pushed.scripts/pages verify-uiexits10when the site answered and the UI is absent, another commit's or not whole — oneFAILline per finding on stderr, each ending with the command that shows it again — and12when the site never answered or no commit could be read to hold the UI to.scripts/pages verify-uialso exits10when the landing page leads with an undecided figure; theFAILline names each figure.- In
.github/workflows/pages.ymlit is the stepthe UI it publishes; a failure fails the deployment. Inscripts/ci/pages-checkit is section 2c of thepages-livegate, whichfinishasks (at-finish) and the scheduled pages run asks every thirty minutes, deploying when it fails.
Verification
test/cases/2983_the_published_ui_is_the_trunks.sh, with no Rust executable and no network:
the model's entry resolves its mount from the topology and the workflow builds and uploads
what it says; verify-ui against a site served locally passes a snapshot read at the commit,
with and without --commit, and refuses by name a stale built_from, a missing
surface.json, a listed page that is absent, a record of another run, a banner of another
commit and a record that does not say what ended its reading, states what a cut-short
snapshot left out and what --max-pages did not ask, and returns 12 for a site that does not
answer; pages-check passes the UI read at the published commit and exits 10 for another
commit's and for none; scripts/site-deploy refuses a site without the snapshot, with
another commit's and with a misplaced one, and publishes HEAD's. test/cases/180 and
test/cases/96 hold the gate and the deploy over a whole site that carries one;
test/cases/1092 and test/cases/1093 hold the producer.