Skip to content

Weigh the docs-site eager closure structurally (#6316) - #6402

Merged
os-warren merged 4 commits into
mainfrom
claude/issue-6316-docs-route-closure-gate
Aug 25, 2026
Merged

Weigh the docs-site eager closure structurally (#6316)#6402
os-warren merged 4 commits into
mainfrom
claude/issue-6316-docs-route-closure-gate

Conversation

@os-warren

@os-warrenos-warren commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#6316

check:eager-closure reads apps/console/dist/eager-closure.json and
performance-budget.yml builds @object-ui/console, so that budget governs the console
bundle. The Next docs site is weighed by nothing — and apps/site/app/components/registerCatalogBlocks.ts
adds side-effect imports to /docs/[[...slug]], the route all 181 docs pages share. Its
only measurement (7738.7 kB / 29 chunks -> 9542.6 kB / 40 chunks) was reconstructed by
hand, once, and #4616's +50% stop condition has had no instrument behind it.

Executing the triage ruling on #6316: shape 2, a cheap structural gate. No docs-site
build in CI, no second byte-budget report, no new ceiling constant.

What the gate does

scripts/check-docs-route-eager-closure.mjs walks the docs route's static module graph
from source — the route entries plus every content/docs/**/*.mdx module (those are
compiled into this route through the generated .source/server.ts) — and classifies every
package the registrar names into exactly one of three buckets:

bucketmeaning
RECORDEDlisted in MEASURED_PAYLOAD — its eager cost was argued for and written down when it landed (#4600, #4616)
FREEalready reachable without this file naming it, so the import adds a declaration and no payload
NEW GRAPHneither — it pulls a graph this route has never carried, and the gate fails

The third bucket is the point: it converts "an unmeasured hazard" into "a review event",
which is what a cheap instrument can honestly do. Green output on main today:

✅ all 13 packages named in apps/site/app/components/registerCatalogBlocks.ts are accounted
for: 11 recorded payload, 2 already reachable (@object-ui/plugin-form <- packages/plugin-view/src/ObjectView.tsx,
@object-ui/plugin-grid <- packages/plugin-view/src/ObjectView.tsx).
✅ MEASURED_PAYLOAD is honest: all 11 entries are still named by the registrar and none is
reachable from the route any other way.
✅ gauge: 1349 modules crawled from 148 route roots (144 MDX), every specifier resolved, the
registrar is on the route, and 18 of 46 workspace packages stay OUTSIDE the closure — so
the traversal still discriminates.

The FREE line is #6314's claim, measured rather than asserted: plugin-form and
plugin-grid are in this route's eager closure through @object-ui/plugin-view's
module-scope import { ObjectForm } / import { ObjectGrid }. The gate names the file.

MEASURED_PAYLOAD is a ledger, not a ceiling. It carries no bytes and no threshold. Its
eleven entries are re-measured on every run: an entry that stops being named, or that
becomes reachable some other way, fails half 2 and has to shrink. Without it the gate would
be red at rest, and a gate that is red at rest is a gate someone deletes.

Both directions, proven

A structural gate that cannot fail is worse than no gate, so the failing direction was
measured, not reasoned about. Planted import '@object-ui/plugin-tree'; in the registrar
on 16c36fbb9:

=== gate exit with the planted package: 1 ===
❌ 1 package(s) named in apps/site/app/components/registerCatalogBlocks.ts add a NEW graph to
the /docs/[[...slug]] eager closure:
@object-ui/plugin-tree (line 184) — nothing else on the route imports it
That route is shared by every docs page, and the budget over it (objectui#4616's +50% stop
condition) has no instrument — which is why this is a review event and not a number. Either
reach the code through a package the route already carries, or argue for the payload and
record it in MEASURED_PAYLOAD in scripts/check-docs-route-eager-closure.mjs with what it is
for.
✅ MEASURED_PAYLOAD is honest: …
✅ gauge: 1351 modules crawled … 18 of 46 workspace packages stay OUTSIDE the closure …

@object-ui/plugin-tree is genuinely outside this route's graph: it is named only by
apps/console — a different app, not on this route — and there only through
ComponentRegistry.registerLazy('object-tree', () => import(...)), a dynamic import, plus
a dev-only manifest dump. apps/site does not depend on it, no MDX file imports it, and no
package in the route's closure imports it. The mutation was confirmed on disk by hash and
line count before the gate was run, restored under a trap … EXIT INT TERM with
git checkout HEAD -- naming the file by absolute path, and the restore proven by the blob
hash matching HEAD:apps/site/app/components/registerCatalogBlocks.ts (595a019c5) with an
empty git diff HEAD.

The gauge half is what makes that credible, and it earned its place while this was being
written: the first version of the traversal never parsed .mdx files, and half 3 caught it
by reporting that the registrar was not reachable from the route at all. The unit test
drives the real analyse() over fixture trees for each vacuity vector — fenced MDX code
blocks, import type, import(), prose in the registrar's own header, an unresolved
specifier, "everything is reachable", and the registrar falling off the route.

Why the workflow is unfiltered

Its inputs are the whole route graph — apps/site/**, content/docs/**, packages/** (a
refactor dropping import { ObjectGrid } from ObjectView.tsx is exactly what turns a FREE
declaration into a new graph) — plus its own closure in scripts/. A paths: filter
listing all of that is indistinguishable from none, and one that misses a directory cannot
be exercised by the PR that changes it (#6321). So the gate is cheap-tier and unfiltered:
checkout plus one node call, no install and no build, ~1.3 s. It is classified in
scripts/dependabot-merge-gate.mjs as a required context and subscribes merge_group.

The page the pin requires (content/docs/guide/ci-cd-pipeline.md)

scripts/__tests__/ci-cd-pipeline-doc.test.ts holds that page against .github/workflows/
in both directions, so a new workflow with no documented section is a red by design —
#3212, where lint.yml gated PRs for months while the page never mentioned it. This PR adds:

  • a section after README Exports, with the triggers, the check name, the
    REQUIRED_CONTEXTS / merge_group relationship, why the workflow is deliberately
    unfiltered, the three-bucket table, and an explicit paragraph that exit 1 is a verdict
    about the registrar while exit 2 says the gauge itself is untrustworthy
    — a reader seeing
    exit 2 must not conclude the registrar is wrong;
  • the Workflow Inventory row, in the table's existing four columns;
  • a bullet in the path-filters list, next to control-bytes.yml / docs-links.yml.

The ## Merge Queue section teaches the requirable direction without naming this
workflow
: merge-queue-reporting.test.ts forbids that section from enumerating current
merge_group subscribers (#4154 — the page points at MUST_SUBSCRIBE_MERGE_GROUP instead of
keeping a second copy that is short by one the day the next gate lands). The
"can never be required, structurally" bullet is deliberately not extended: this workflow
is the opposite case, and a claim line there would demand a STRUCTURAL_BLOCKS entry for a
claim that is false.

Facts re-measured rather than inherited

  • DEFAULT_REPORT_PATH is at scripts/check-eager-closure-budget.mjs:314 on b116a0684,
    not :292fix(ci): fail Bundle Analysis when its ceilings were replaced on the base branch #6315 rewrote that file. The finding it anchors is unchanged: the path is
    still apps/console/dist/eager-closure.json.
  • The hand-taken 7738.7 kB / 29 chunks figure is quoted as history only. Nothing here
    reads it, re-takes it, or depends on it staying true.
  • The registrar reaches the docs route only through MDX
    (content/docs/guide/schema-catalog.mdx -> SchemaCatalogIndex -> SchemaThumbnail), so
    the MDX half of the traversal is load-bearing rather than thorough.

Verification

On 638d706b0 (head, after merging main at d66316e0c):

  • pnpm exec vitest run scripts/__tests__ — the whole tree: 80 files, 2302 tests, all
    pass
    . That full run is what caught the ci-cd-pipeline.md's "Five workflows subscribe" merge_group list is short by one (skills-paths.yml, since #3735) — the #3261 shape, one subsystem over #4154 pin above; the targeted set could not, since
    both pins fire on the mere existence of the new workflow file.
  • pnpm check:docs-route-closure — exit 0, and the closure is unchanged by everything the
    merge brought into packages/** and content/docs/** (still 1349 modules, 11 recorded /
    2 free, 18 of 46 packages outside). Re-run, not assumed.
  • node scripts/check-doc-links.mjs (17 scan roots), pnpm check:doc-fences,
    pnpm check:doc-types (184 doc files), pnpm check:control-bytes (5291 files) — all exit 0,
    plus a grep -naP control-byte self-scan of the edited page.

On 16c36fbb9 (the gate itself, unchanged since):

  • pnpm check:docs-route-closure exit 0; exit 1 with the planted package.
  • examples/schema-catalog/test/catalog-gallery-render.test.tsx +
    plugin-dashboard-gallery-render.test.tsx (they read the registrar) — 2 files, 586 tests,
    all pass.
  • pnpm type-check:scripts — exit 0.
  • pnpm check:control-bytes, check:entry-guard, check:pre-install-import-graph,
    check:esm-specifiers, check:shell-escape-residue — all exit 0. check:control-bytes
    went red first, on two raw U+0000 bytes in the new test's absent-file sentinel; the
    sentinel was removed rather than escaped (an absent file is now null).
  • ESLint over the four changed source files — 4 files linted, 0 errors, 0 warnings.
    Deliberately narrowed from the repo-wide eslint .: the population came from ESLint's own
    config (isPathIgnored says none of the four is ignored), the count from --format json,
    and eslint.config.js enables no type-aware linting (no project / projectService), so
    this diff cannot move the verdict on a file it did not touch. The full farm runs in CI
    regardless.

Generated by Claude Code

os-warrenand others added 2 commits August 25, 2026 20:06
`check:eager-closure` reads `apps/console/dist/eager-closure.json`, so the
budget objectui#4616 set over `/docs/[[...slug]]` — the route every docs page
shares, and the one `registerCatalogBlocks.ts` adds side-effect imports to —
was governed by nothing.
`scripts/check-docs-route-eager-closure.mjs` walks that route's static module
graph from source (no install, no build, ~1.3 s) and classifies every package
the registrar names: RECORDED payload, FREE (already reachable, so the import
adds a declaration and no bytes), or NEW GRAPH, which fails.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
…ixtures
`pnpm check:control-bytes` found two raw U+0000 bytes in the new test's
absent-file sentinel — the gate's own failure mode, in the file adding a gate.
The fixture map now types an absent file as `null`, which needs no impossible
character at all, and `analyse()` declares its `ledger` parameter so a fixture
ledger is assignable under `allowJs` inference.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
claudeand others added 2 commits August 25, 2026 20:31
`scripts/__tests__/ci-cd-pipeline-doc.test.ts` pins `content/docs/guide/
ci-cd-pipeline.md` against `.github/workflows/` in both directions, so a new
workflow with no section is a red by design (objectui#3212: `lint.yml` gated
PRs for months while the page never mentioned it).
Adds the section (triggers, why it is unfiltered, the three buckets, and why
exit 1 and exit 2 must not be read as one), the Workflow Inventory row, and the
path-filter bullet. The merge-queue paragraph teaches the requirable direction
without naming the workflow: that section may not enumerate current
`merge_group` subscribers (objectui#4154), which the full scripts/__tests__ run
caught.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
@os-warren
os-warren marked this pull request as ready for review August 25, 2026 20:51
@os-warren
os-warren added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 6d551e4Aug 25, 2026
28 checks passed
@os-warren
os-warren deleted the claude/issue-6316-docs-route-closure-gate branch August 25, 2026 21:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: nothing measures the docs-site eager closure that #4600's budget is about — check:eager-closure weighs apps/console only

2 participants

@os-warren@claude