Skip to content

Repair the published docs site, and make its drift fail the build - #632

Merged
xmap merged 11 commits into
mainfrom
worktree-docs-site-repair
Aug 9, 2026
Merged

Repair the published docs site, and make its drift fail the build#632
xmap merged 11 commits into
mainfrom
worktree-docs-site-repair

Conversation

@xmap

@xmap xmap commented Aug 9, 2026

Copy link
Copy Markdown
Owner

A verified sweep of the 801-page published site produced 125 findings. This fixes them, and adds the guards that would have caught them.

The pattern

Generated content was correct; the hand-typed content beside it had drifted. Budget shipped as the 18th BC on 2026-07-13 and the fallout was visible five ways, including a live 404 from a table row whose own legend promised it was navigable. Nothing in CI could catch any of it: the 61 dead-anchor messages were INFO, which --strict ignores, and no test asserted that a shipping BC has a page.

Commits

Commit What
e8ae45c8eb BC counts generated. Home said seventeen, the model page generated eighteen. Adds arch:count kind=planned-bc and widens the hook gate so a home-page marker actually expands
e2b03a0ae2 Budget module page, written from the aggregate docstring, plus a fitness test asserting every BC has a page, a nav entry, and a card
f8d9a7882e _slices() discovers get_fixture_pidinst, a shipped REST+MCP surface that rendered nowhere. Verified it admits exactly one slice: 294 to 295
f05885b511 16 broken API examples, each validated against the JSON Schema FastAPI publishes, plus a test that keeps them honest
57ea3a9775 Ratification and LanguageModel documented, 18 missing events, agent fleet reconciled to 13, budget caps corrected from "declarative" to enforced
61ecce5df9 61 dead anchors to 0. One generator change fixes 56 pages; 35 links labelled "Inventory" now name the pages they actually reach
b0584c62df Pilot numbers get a source or are withdrawn. Filter-foil slot indices corrected from values the runtime would refuse
d53b259175 Six Roles not five, Tango and three more shipped deps on the stack pages, two Deferred rows that had shipped, Cedar reworded, Family.settings_schema
79481287b9 SECURITY, a run-it-locally page sliced from the README, a cite page generated from CITATION.cff, a reply route on 80 questions pages, orphans hidden
6eafe60257 The guards: validation.links.anchors, nav.omitted_files, docs build on PRs, em-dash rule extended to published markdown
c8241b13a8 Reconciles the shutter caveat with #626, which landed mid-branch

Verification

  • make docs-build clean with both validation keys on, 0 warnings.
  • pytest tests/unit tests/architecture: 43,249 passed, 622 skipped.
  • Both guards proven to bite by planting a dead anchor and an un-navved page and watching the build abort, then reverting.

Worth your attention

A real finding about the beamline, not the docs. 2bm-docs#171 closed as completed by taking Path C: 2bma:table1 was deleted, not repaired, so its composite axes no longer exist in Channel Access. The pilot descriptor still models MirrorTable as a virtual_pose asset over that record. Documented and raised as MODE-3; not re-modelled, since that is a design decision with test implications.

Three items are noted rather than fixed, by explicit decision: the MirrorTable model above, the objective focal lengths that survive in the scenario fixture, and the three coexisting shutter PV forms.

Three findings were dropped with reasons: an MCTOptics claim the repo cannot verify, the em-dash and emoji findings (refuted, they were table-cell placeholders and a text dingbat), and un-hiding the blog.

An adversarial verification pass over the riskiest commit found eight defects in my own work before it was committed, including a safety claim that was wrong in the unsafe direction.

🤖 Generated with Claude Code

xmap and others added 11 commits August 9, 2026 11:45
The home page said seventeen bounded contexts while the architecture page
one click away generated eighteen from the code model, and the model page
promised two reserved BCs above a table showing one. Both numbers were
hand-typed, and both went stale the day budget shipped its Allocation
aggregate.

Neither is hand-typed now. arch:count grows a planned-bc kind that reads
len(_PLANNED_ROWS), so the reserved-BC clause tracks the table it
introduces, and the home page carries the same kind=bc marker the model
page already used.

That marker only expands on pages the hook reaches, and the hook was
gated on the architecture/ prefix alone, so a marker on the home page
would have rendered its stale checked-in body forever while looking
generated. The gate now admits index.md, and the marker walk covers it,
so the guard and the gate cannot drift apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Model page lists budget/allocation as Active, and that page's status
legend says Active means the aggregate ships and is listed under Modules.
It was not: /architecture/modules/budget/ returned 404, with no nav entry
and no card, for the four weeks since the Allocation aggregate shipped.
Every other of the eighteen BCs had a page, so the one BC a reader could
not follow was the one governing what the deployment is allowed to spend.

The page is written from the aggregate's own docstring, which already
held the FSM, the five slices, and the rule that balance is never stored
but folds from the inference ledger. Its slices table is the generated
marker, so the surface cannot drift from the routes.

A fitness test now asserts the three things that must move together for
a BC to be readable: the page exists, the nav points at it, and the
landing grid cards it. Nothing checked any of them before, which is why
a promise the site makes about itself could go unmet in silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_fixture_pidinst registers GET /fixtures/{fixture_id}/pidinst and an
MCP tool of the same name, but it carries neither command.py nor
query.py, and the introspector required one of the two. So a shipped
public surface rendered nowhere on the equipment page, and the prose
went on describing get_asset_pidinst as the only way to reach the
PIDINST serializer.

The failure was silent, which is the part worth fixing: a slice that
falls through the discovery rule produces no build error, just a table
one row short. Discovery now also accepts route.py plus handler.py, the
shape a read slice too thin for a query takes.

This is the only slice in the tree the old rule dropped, verified by
counting both rules over every BC: 294 before, 295 after, the single
difference being equipment/get_fixture_pidinst. The pinned equipment
slice count moves with it in the same commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each module page's Examples section carries an HTML comment claiming the
block was extracted from the contract tests. It was not, and sixteen of
them had drifted far enough that a reader copying one got a 422 or a 404.

Three documented endpoints did not exist in any form: /procedures/{id}/steps
is /activities, /runs/{id}/readings is /observations, and /plans/{id}/wires
is /add-wire with a sibling /remove-wire rather than a DELETE. The rest were
field-level: decision sent actor_id where the request takes decided_by,
federation sent scope_set for scopes, caution sent {target_kind, asset_id}
for the {kind, id} discriminator, safety sent id for an External binding's
value, campaign sent id for an external ref's value, two batch endpoints
were documented as a single flat entry rather than an entries list, methods
omitted the required execution_pattern, and equipment and recipe used
affordance names (Move.Continuous, captures_images) that are not members of
the thirty-value enum the site's own reference page publishes. Federation
told the caller to send signed_at, which the handler stamps from the clock
port and the model rejects.

Every body is now validated against the JSON Schema FastAPI publishes for
that route, and a test does the same on every run so the examples cannot
drift from the surface again. Verified the guard by breaking one example
deliberately: it fails on a defect the API would reject, and stays quiet
for an extra field on a model that permits extras, which is the API's own
semantics rather than a gap in the check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two whole aggregates were publishing endpoints with no explanation
anywhere on their page. Trust's opening sentence generated "six
aggregates" from the code model and then named five: Ratification, the
co-signature gate, appeared nowhere in the docs despite three live
endpoints and its own enforcer agent. Agent had the same hole for
LanguageModel, whose five slices rendered in the generated slices table
while every hand-written section covered only the Agent aggregate.

Eighteen shipped events were missing from seven Events tables, some for
two months. Those tables are the only per-event reference on the site, so
an integrator reading the log met types the docs did not list. The count
came from diffing the introspected model against each page rather than
from the audit's estimate, which was high by two.

The agent fleet was stated three ways, none of them right: five on the
modules card and in decision's prose, nine on the agent page, thirteen
seeded in the composition root. It reads thirteen now, with the four
undocumented agents added and the two that register unconditionally
called out, because a kill switch and a consequence gate that an operator
can forget to switch on are not what they claim to be.

Also: agent budget caps are enforced, not declarative, and the page said
the opposite of a safety-relevant truth. Run advertised response bodies
it does not return. Equipment pointed at two serializer files that moved
into a package. Standards listed Supply as planned and DSSE PAE as
pending, both shipped. Federation's card rendered a raw icon shortcode,
the only unresolved one on the site. Every module page linked to a
"Reading the examples" section that did not exist, and the landing page
sent readers to an Idempotency column no slices table has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fifty-six model pages ended by promising an explanation: a runbook and
live experiment view would be invention, "see the note on the index".
The note was deleted in July when the index pages became generated, and
the sentence kept pointing at an anchor no page has carried since. It was
the most repeated broken link on the site.

The generator emits the section again rather than editing 56 files, keyed
on maturity so every non-pilot beamline gets it and 2-BM, which really
does have both pages, does not.

Thirty-five Open questions pages sent the reader to a link labelled
Inventory. The stages migration dissolved that page everywhere except
2-BM, and the href was repointed while the label was not, so the link
resolved cleanly to a page holding no device tables at all. Nothing
flagged it: not a 404, not a missing anchor, so both the build and a link
checker passed a link that lied about its destination. They name the
device pages now. 2-BM's real Inventory link is untouched.

The last five were renamed headings the inbound links never followed:
two on the 2-BM Controls page, three on ID32, and one pointing at a
2-BM beam-alignment section that was never written, now aimed at the
Beam mode section where the blocker it means is actually tracked.

The build reports zero missing anchors, down from sixty-one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 2-BM pages published physical values with nothing behind them. Nine
Hexapod performance figures appeared in one prose table and nowhere else
in the repo, though git shows they once carried a datasheet attribution
and a caveat that they are dominant-axis envelope figures rather than
per-DoF measurements. Both were dropped when the zone pages were
consolidated. The values move into the descriptor with that attribution
restored verbatim, matching how the Rotary entry beside them already
cites its own datasheet.

The three objective focal lengths get the opposite treatment, because
nothing can source them: they appear only in CORA's own scenario, which
requires the field, and the descriptor, the catalog row and the staff
pages are all silent. They are withdrawn from the published tables and
asked as DET-14 rather than left reading as recorded fact.

The filter-foil table published its Position values in the Slot index
column, so it told an operator to command index 106. Both paddles are
now indexed 0 to 4, and the operational upstream m17 paddle is published
for the first time, having existed only in the executable model.

MODE-3 was the sharpest correction. 2bm-docs#171 did close as completed
on 2026-06-16, but not by fixing the substitution: upstream took Path C
and deleted the 2bma:table1 record, so its composite axes no longer exist
in Channel Access. CORA still models MirrorTable as a virtual_pose asset
over that record. The pages now say what the tracker actually contains
and ask the question that follows, which is whether to re-model onto the
raw motors, not when the fix will land.

Shutter PV strings are unchanged, as intended. The descriptor notes and
the recipe step tables now say what the bare prefixes are: device-level
names whose concrete record leaf is still unconfirmed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
These three sections state CORA's own rules and picks, so drift here is
expensive: a reader follows them into the code and finds something else.

The catalog hub called the Role registry closed at five and named five,
while the generated Roles page beside it listed six. Shutter graduated on
exactly the rule-of-three trigger that paragraph describes, three Methods
needing to command a shutter as a bound contract, and the page still filed
shutters under the deferred Conditioner cluster it had left. Two columns
the descriptor validates were also being dropped on the way to the page:
executor_shapes, which the hub elevates to a governance rule, and the
per-method note that carries the Role-versus-Family binding rationale.

The stack pages under-described what ships. The ControlPort speaks Tango
as well as EPICS, with a production adapter and its own extra, and a
Tango-floor reader would have concluded otherwise. globus-sdk, h5py and
botorch back three more ports and appeared on no stack page at all. Two
rows read Deferred for things that shipped: the container image, which
three other pages already document, and the boot-time schema-version
assertion, which build_kernel performs before anything that can write
exists. Cedar was presented as the Decision BC's policy language; there is
no Cedar dependency and no policy to author, only a borrowed noun.

The reference pages had four wrong facts and two missing children.
conventions.md named capability.settings_schema as the settings declarer,
which resolves to a real aggregate in the wrong BC; it is Family. It also
guaranteed that exactly one slice is exempt from the subject guard, where
three are. layout.md published four of the eight Port carve-outs and
pointed at a registry that has doubled. patterns.md cited a slice renamed
away from its re- prefix. The Reference index listed eight of its ten
pages, omitting Affordances, which the Catalog links into, and Naming,
which is the rule set reviewers are sent to.

Counts that had drifted are refreshed, and the one I could not derive
cleanly is gone rather than replaced with another guess. The docs version
pin for mkdocs-material is likewise removed rather than corrected: it
already disagreed with the two places that install it, and a third copy
is a third thing to drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d not

Four things a reader needs existed only in files the site never rendered.
SECURITY.md carries the disclosure channel and the sharpest statement of
the X-Principal-Id trust contract; the README carries the only copy of the
commands that install and start CORA; CITATION.cff carries the citation
metadata. All three are now staged or generated rather than retyped, so
the site cannot disagree with them: run.md is sliced from the README's own
sections and fails the build loudly if either heading is renamed, and the
cite page is rendered from CITATION.cff at build time.

Eighty Open questions pages asked beamline staff to confirm facts and told
them nothing about how to answer or what Blocks-go-live means. Only 2-BM
did, and it keeps its own richer version. The rest get a shared footer
appended by the hook, so no hand-authored file changed and a new
deployment inherits it. The footer carries no anchors: on 83 pages, one
bad fragment is 83 bad fragments.

Two pages were reachable only by search. The projects memo is internal
working material that addresses the maintainer directly and cites private
memory filenames; it is excluded. The module-page template moved out of
docs/ entirely, because not_in_nav only hides a page from the nav while
leaving it built, sitemapped and indexed, which is how a placeholder
skeleton became publicly searchable. The scrubber demo's 29 KB script and
its data file stop shipping on all 802 pages.

The footer names the license. It links nothing else: mkdocs does not
depth-rewrite copyright links, so the relative one I tried first resolved
against the current page and 404d everywhere except the root.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every fix in this branch was findable only by reading the rendered site.
Nothing in CI could have caught any of it, and that is the part worth
changing.

mkdocs 1.6 ships a validation config this project never set. Turning on
links.anchors escalates a dead fragment from INFO, which --strict ignores,
to WARNING, which it fails on; that class alone accounted for 61 of the
audit's findings. nav.omitted_files does the same for a page that builds
and enters the sitemap and the search index while no nav entry reaches it,
which is how an internal memo became publicly searchable. Both are clean
now only because the earlier commits cleared them, and both were verified
by breaking the build on purpose and watching it fail: a planted anchor
and a planted orphan page each aborted the run.

The docs workflow only ran on push to main, so a broken build was
discovered after it shipped. It now builds on pull requests too. The
deploy job gets `if: github.event_name != 'pull_request'` in the same
change, because it has `needs: build` and no other condition, so adding
the trigger alone would have published every PR's build to the live site.
The talks decks and the artifact upload are skipped on PRs: nothing
deploys from there, and a chromium download per deck is a lot to pay for
a docs check.

The no-em-dash rule covered tracked Python and not the pages that face
readers, which is where the rule is actually about. Sixty-six em dashes
across three published files were table cells meaning "not applicable"
and now say n/a. The markdown check ships without an allowlist: the
Python side has one because the rule was retrofitted onto a large corpus,
whereas markdown starts clean, so a future hit is new and should be fixed
rather than recorded. Its helper is git-aware and keeps the GIT_DIR strip
that pre-commit needs, which the migration-file sibling omits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
While this branch was in flight, #626 pointed the 2-BM shutter PVs at the
records that exist: the descriptor now carries S02BM-PSS:FES:BeamBlockingM
and the SBS twin rather than the bare prefixes.

The earlier commit here added a caveat built on the opposite premise, that
the pv is a record prefix and not a readable channel. The rebase merged
without a conflict and left that text sitting two lines under a pv it now
contradicts, which is the failure mode a clean merge is worst at showing.

The caveat now describes the full record and its inverted read, and keeps
the part that is still true: the leaf name is CORA-proposed and awaits APS
PSS sign-off. The recipe step tables say the same, so their open and closed
values read as the operator's words for == 0 and == 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Coverage report

This PR does not seem to contain any modification to coverable code.

@xmap
xmap merged commit e2f5403 into main Aug 9, 2026
19 checks passed
@xmap
xmap deleted the worktree-docs-site-repair branch August 9, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant