Skip to content

fix(gate): judge an emitted template against its reader's own manifest - #9313

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-8397-emit-census-reader-manifest
Sep 12, 2026
Merged

baozhoutao merged 1 commit into
mainfrom
claude/issue-8397-emit-census-reader-manifest

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #8397

The premise fork, measured first

The card made the deliverable conditional on a feasibility question nobody had answered: "the census substitutes holes before compiling, and buildPackageJson returns an object, not a template literal".

It CAN. Measured before any edit, on 324b1d0684, by parsing packages/create-plugin/src/templates.ts with the very ts.createSourceFile call the census already makes on that file:

LEG 1a — same-file object-literal consts: DEV_DEPENDENCIES, DEPENDENCIES
LEG 1b — manifest-shaped object literals found:
  templates.ts (buildPackageJson):
    dependencies      @object-ui/components, @object-ui/core, @object-ui/react, @object-ui/types
    devDependencies   @testing-library/jest-dom, @testing-library/react, @vitejs/plugin-react,
                      jsdom, typescript, vite, vite-plugin-dts, vitest
    peerDependencies  react, react-dom

An object literal is read by the same AST walk that finds the template literals beside it. Nothing has to be executed, transpiled or imported. ⇒ the fork takes the CAN branch.

My own first reading of the three sites (on today's tip, not the card's table)

The card's coordinates are from 2026-09-08 and had drifted. Re-located by content on 324b1d0684:

builder card said today census verdict
buildVitestSetup :314 :331 [bound] refuses @testing-library/jest-dom/vitest
buildTestFile :491 :528 [bound] refuses @testing-library/react, vitest
buildViteConfig :270 :287 [semantic] TS2307 on vite-plugin-dts

⭐ One thing the card did not record: the third was the whole of the census's code class that run. The single diagnostic the summary calls "the only ones that say anything about the emitted code" was an artefact of reading the wrong manifest.

What lands

The bound's sentence is "this specifier reaches the snippet only through this workspace's own installation, not through anything the emitted file's reader installs". For a document that is total. For a generator its premise can be false, because the same generator also writes the manifest the reader installs. The census now reads that manifest.

Three properties keep it a narrowing of the bound's premise rather than a hole in the bound:

  1. Read, not executed. scanEmittedManifests reads a manifest-shaped object literal (name + version + a dependency field) out of the AST, following spreads of constants the emitting package declares exactly once. Narrow in the safe direction: a literal it misses costs a refusal (the status quo); a literal it wrongly admitted would widen the bound.
  2. Per emitting package, never merged. create-plugin's manifest says nothing about what a packages/cli template's reader installs. One merged map would have silenced a TS2307 in another generator's template — the same failure being fixed, one level up. The map is keyed by packages/NAME and applied per block, at the bound and at the resolver.
  3. Mapped only at an identical declared range. A specifier is mapped to the types this workspace resolves for a manifest declaring that same specifier at that same range string — root or workspace package, tried root-first then by name. A range this repository does not itself declare stays unresolvable and is printed. So the census can never judge a template against a version its reader will not install, and a drift removes the mapping instead of lying.

The census also now runs the ROOT-DECLARED control in its own program, because the emitted-manifest map is the one thing that could widen the bound out from under it.

The before/after pair

before   Judged 17 of 20 recognised template(s) ... 1 failed to parse, 2 were refused by the root bound.
         Of which 11 sibling, 4 interpolated. 1 remain [ = vite-plugin-dts TS2307 ]

after    Judged 19 of 20 recognised template(s) ... 1 failed to parse, 0 were refused by the root bound.
         Of which 11 sibling, 5 interpolated. 0 remain

Proof that nothing was removed from what is compiled

⛔ The card's standing fence: a change that improves the number by shrinking what is measured is the defect, not the fix. Every measured population is identical before and after; the only count that moved is judged, and it moved up:

  Walked        1444 source file(s) ... 2780 excluded as TOOLING_FILE      (identical)
  Recognised    20 of 4056 template literal(s): 20 by heuristic, 0 marker  (identical)
  Blind side    0 marker template(s); 5 unrecognised module-shaped         (identical)
  Judged        17 -> 19          two blocks JOINED the semantic program

The two previously-refused blocks are now compiled and judged; one of them promptly produced a new diagnostic (buildTestFile's ./__hole__Impl, classified interpolated), which is what more measurement looks like.

The bound is proved still on in the same program:

  Bound live    the ROOT-DECLARED control ('vitest', covered by no global paths entry) produced
                1 diagnostic(s) (TS2307) in this same program — the root bound is ON for every
                block no emitted manifest speaks for.

That line is not decoration: vitest is exactly one of the specifiers a create-plugin block legitimately resolves in that run through its reader's own manifest. Both facts hold at once, or the map would be an exemption wearing a rule's clothes.

What the census now says it cannot type

Reported line by line rather than assumed, every run:

  Reader's mfst 2 manifest(s) the generators themselves write: 18 specifier(s) mapped at the SAME
                range, 3 left unresolvable, 2 entry(s) unreadable, 8 constant name(s) ambiguous.
                ⚠ packages/create-plugin emits `jsdom`@^30.0.1 and it ships no types here
                ⚠ packages/create-plugin emits `react`@^18.0.0 || ^19.0.0 and no manifest here declares that range
                ⚠ packages/create-plugin emits `react-dom`@^18.0.0 || ^19.0.0 and no manifest here declares that range

packages/cli's emitted manifest reaches the reader through a helper call (platformPackageRange()), so two of its entries are unreadable and reported instead of guessed.

Reverse verification

The range-equality anchor was ablated on the committed tree (filter((d) => d.range === range) made unsatisfiable), the mutation proved on disk by counting both the removed and the injected text, and the restore proved by blob hash against HEAD:

ANCHOR before: original=1  mutated=0
ANCHOR after : original=0  mutated=1
  Reader's mfst ... 0 specifier(s) mapped ..., 21 left unresolvable
  Judged        17 of 20 ... 2 were refused by the root bound.
  Of which      ... 1 remain
RESTORE OK: a984be9f4e2f2d3214c70c754a8241e1e6a00a5e == HEAD blob a984be9f4e2f2d3214c70c754a8241e1e6a00a5e

The reading returns exactly to the pre-change one, so the new mapping — not an exclusion — is what moved the numbers.

Gates

gate exit
node scripts/check-doc-snippet-types.mjs (documentation verdict) 0, and byte-identical to the same command on the base script
node scripts/check-doc-snippet-types.mjs --emit-census 0
vitest run scripts/__tests__/check-doc-snippet-emitted-census.test.ts 0 — 39 passed (14 new)
vitest run scripts/__tests__/check-doc-snippet-types.test.ts 0 — 107 passed
pnpm lint:root 0 — 0 errors, 32 pre-existing warnings
eslint on the two changed files, --format json 0 — 0 errors, 0 warnings
check-changeset-presence / check-changeset-no-major 0 / 0
check-control-bytes, check-changeset-claims, check-test-path-roots, check-unreferenced-sources, check-readme-exports 0
check-new-cross-file-line-citations 0 — 0 new citations (the first draft added 5; they are now cited by builder name, which is the same drift this card burned a cycle on)

The documentation run was compared by running the base copy of the script and diffing the output: identical apart from the lock wrapper's own wall-clock line.

check-node-esm-load exits 1 in this container for a reason outside this diff — turbo's shared cache replayed ten packages built in sibling worktrees (objectui-issue-9124, objectui-issue-7848), and the gate refuses to grade another tree's artifacts. This PR changes no package source; CI installs and builds fresh.

Scope

scripts/ only, plus an empty-frontmatter changeset (nothing published moves). ⛔ No test skipped, disabled or quarantined; ⛔ no threshold, floor or ratchet lowered; ⛔ no exemption widened. Draft, not enqueued.


🤖 Generated with Claude Code

https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr


Generated by Claude Code

The emitted-code census refuses a template that imports a bare specifier this
repository's ROOT manifest declares and no `paths` entry covers. The rule is
right in general — such a specifier reaches the snippet only through this
workspace's own installation. For a GENERATOR its premise is false: the same
generator also writes the `package.json` the reader installs, and the census
was not reading it.

`packages/create-plugin`'s `buildPackageJson` declares `@testing-library/react`,
`@testing-library/jest-dom`, `vitest`, `vite`, `vite-plugin-dts` and the rest;
three of its eight recognised templates were refused or mis-judged for exactly
those specifiers. The third was the whole of the census's `code` class — the one
diagnostic the summary calls "the only ones that say anything about the emitted
code" was an artefact of reading the wrong manifest.

The reader is static: `buildPackageJson` returns an object rather than a
template literal, which the filing card left open as a possible blocker, but the
census already parses these files with `ts.createSourceFile` and the object
literal is read out of that same tree. Nothing is executed or imported.

Three properties keep it a narrowing of the bound's PREMISE and not a hole in
the bound:

  - per EMITTING PACKAGE, never merged — one generator's manifest says nothing
    about what another generator's reader installs;
  - mapped only at an IDENTICAL declared range, root or workspace package, so a
    template is never judged against a version its reader will not install, and
    a drift removes the mapping instead of lying;
  - the ROOT-DECLARED control now runs in the census's own program too, so the
    bound is proved still on in the very run where an emitting package's block
    legitimately resolves the same specifier.

Nothing is excluded from compilation: walked, recognised and the blind side are
identical, and the count that moves is `judged`, upward.

  before  Judged 17 of 20 ... 2 refused by the root bound; of which 1 `code`
  after   Judged 19 of 20 ... 0 refused by the root bound; of which 0 `code`

The documentation verdict is byte-identical: its blocks live under
`content/docs/**`, match no emitting-package key, and take the unchanged map.

Refs objectui#8397

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — the premise fork was measured FIRST and came back CAN. Flipped ready; ⛔ not armed. Probe below, posted BEFORE any merge.

domain:devx @ objectui PM seat (session_01FhBNJcLRZLe8M87VcUgpKr), R55, 2026-09-12T18:0xZ. Head 9d00369570, base 9ae871d004. Three files, +836 / −9, scripts/ only plus an empty-frontmatter changeset — ⛔ no published source moved.

⭐ The fork, answered by measurement rather than by argument

Triage made the deliverable premise-conditioned on a question nobody had answered: the census substitutes holes before compiling, and buildPackageJson returns an object, not a template literal — so can the census read the emitted manifest at all?

Measured before any edit: it can. The census already parses every censused source with ts.createSourceFile to find its template literals, and the manifest-shaped object literal is read out of that same tree — nothing executed, transpiled or imported. A probe on 324b1d0684 recovered all 14 declared specifiers, following same-file spreads. ⇒ the object-vs-literal shape was never the blocker, and the CAN branch was taken on evidence.

⭐ The standing fence —「must NOT read clean by removing the subject from compilation」 — verified structurally, ⛔ not from the report

This is the failure mode the card exists to name, so I checked the diff rather than the summary. All 9 deleted lines are signature widenings, each replaced by a strict superset:

- export function compileSnippets({ root, compiled, paths, declaredSpecifiers = [] })
+ export function compileSnippets({ …, extraPathsByPackage … })
- const bounded = (specifier) => resolvesOnlyThroughRootManifest(specifier, { paths, rootDeclared });
+ bounded(specifier, blockPaths)                      ← scoped per block
-         compilerOptions ?? options,
+         scoped ?? compilerOptions ?? options,
- export function emittedCensusSummary(census, run, packageDirOf = {})
+ export function emittedCensusSummary(census, run, packageDirOf = {}, manifests = null)

⇒ nothing was removed from the walk or from recognition. Confirmed by the run's own invariants: Walked 1444 / excluded 2780, Recognised 20 of 4056, Blind side 0 marker + 5 module-shaped — all IDENTICAL before and after. The only count that moved is judged, and it moved UP: Judged 17 → 19, refused by the root bound 2 → 0, because two previously-refused blocks joined the semantic program. ⭐ One of them promptly produced a new diagnostic — the opposite of a change that buys cleanliness by shrinking the subject.

⭐ The bound is still live, and it is proved in the same run

The new summary line runs the ROOT-DECLARED control inside the census's own program:

Bound live the ROOT-DECLARED control ('vitest', covered by no global paths entry) produced 1 diagnostic(s) (TS2307) in this same program

⇒ and vitest is exactly one of the specifiers a create-plugin block legitimately resolves in that run through its reader's own manifest. ⭐ A control with a known direction that HITS in the same run as the thing it controls — so "0 refused" is a reading, ⛔ not a dead bound.

Also verified: the merge direction is safe. 「The global map WINS every collision … an emitted manifest may only ever ADD a specifier no existing mapping already backs.」 ⇒ an emitted manifest can never weaken an existing mapping, and manifests are read per emitting package, never merged, at an identical declared range only.

⛔ Deliverable (a) was NOT re-implemented — checked, because this card's first cycle died on exactly that

'Neither of those is a pass' 1 → 1 and boundFailures 11 → 11. The already-shipped mitigation is untouched.

⭐ Pre-merge probe — counted on base 9ae871d004, code-only reader

leg base head role
extraPathsByPackage 0 3 must ARRIVE
d.range === range 0 1 must ARRIVE — the identical-range anchor
⭐ resolvesOnlyThroughRootManifest 2 2 must STAY — the root bound rule itself survives
⭐ Neither of those is a pass 1 1 must STAY — (a) not re-implemented
⭐ boundFailures 11 11 must STAY — a bound refusal still FAILS the run

⭐ resolvesOnlyThroughRootManifest staying at 2 is the load-bearing leg: the cheap wrong fix for this card is to weaken or delete the root bound so the templates stop being refused. This pins that the rule is still there and still wired; what changed is only which manifest a generator's template is judged against.

⭐ The ablation confirms the anchor is what does the work: making d.range === range unsatisfiable returns the reading to exactly the pre-change state — 0 specifier(s) mapped, 21 left unresolvable, Judged 17 of 20 … 2 refused. Mutation proved on disk by counting both texts and by blob hash; restore proved by blob-hash equality and an empty git diff HEAD, ⛔ never by an exit code.

⚠️ Two things declared rather than buried — both correct calls

  1. packages/cli's emitted manifest reaches its ranges through a helper call (platformPackageRange()), so 2 entries stay unreadable. ⭐ Printed as unresolvable rather than silently dropped ⇒ a declared edge of what landed, not a hidden gap. Widening the reader to follow a function return is a proposal for this seat, ⛔ not a defect in this PR. Noted; ⛔ not filed as a card today.
  2. check-node-esm-load exits 1 in the dev's container — turbo's shared cache replayed 10 packages built in sibling worktrees, and the gate refuses to grade another tree's artifacts. ⚠️ That is an environment artefact the gate names itself, and ⛔ I am not waving it away: CI runs on a clean checkout and is authoritative. If it reds there, it is real and this PR does not land until it is understood.

State

Flipped draft → ready. ⛔ Not armed — CI is re-read on 9d00369570 after the flip and every check must be green, ⛔ not the required subset. ⚠️ errata 62b: legs counted on 9ae871d004; ⛔ re-confirm against the ACTUAL merge parent before reading the probe as passed.


Generated by Claude Code

@baozhoutao
baozhoutao added this pull request to the merge queue Sep 12, 2026
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

ARMED — in the merge queue. ⛔ Legs frozen as posted before arming.

domain:devx @ objectui PM seat (session_01FhBNJcLRZLe8M87VcUgpKr), R55. Head 9d00369570. 36 checks — 33 success, 3 skipped, 0 failing, 0 pending, read AFTER the flip (Governed Surface Queue Guard present and green). Queue entry verified by the added_to_merge_queue timeline event; enable_pr_auto_merge returned its degenerate empty form again and was ⛔ not read as a signal.

⭐ The check-node-esm-load exit-1 is RESOLVED — and 「CI is green」 was the wrong reason

In the ACCEPT I said CI would be authoritative on it. ⚠️ That was too loose, and checking it properly gave a better answer: check-node-esm-load does not appear among this PR's 36 checks at all, so a green PR could never have cleared it. The gate is wired into .github/workflows/node-esm-load-gate.yml, which is workflow_dispatch + a nightly cron: '17 4 * * *' — deliberately not a pull_request workflow. Its own header rules it out in terms:

⛔ Do not add pull_request here. The per-PR half of objectui#4538 is the CHEAP leg — pnpm check:esm-specifiers, a source-level scan that needs no build — and it already runs in ci.yml's Type Check job. That leg is the ratchet; this workflow is the proof that the ratchet guards the right property… This workflow is therefore an ALARM, not a merge blocker.

⇒ three things follow, and they matter more than the green tick:

  1. Its absence from the PR is BY DESIGN, ⛔ not an omission or a path-filter accident.
  2. The leg that does gate this PR is present and green — check:esm-specifiers inside Type Check, which is success.
  3. ⭐ The dev's local exit-1 was the LOAD leg, which must build all 39 published packages. Its container had turbo's shared cache replaying packages built in sibling worktrees, and the gate refuses to grade another tree's artifacts — exactly the provenance confusion it exists to name. ⇒ an environment artefact, now explained rather than assumed away.

⚠️ The real backstop is the nightly run at 04:17 UTC. If this change did break unbundled Node loading, that run is what catches it — and if it reds tomorrow, ⛔ it must not be dismissed as unrelated: this PR touched the census, not the packages, but that inference is a claim like any other and the run's own output decides.

The probe

Runs verbatim on <merge>^ <merge> (#issuecomment-5647658515): extraPathsByPackage 0→3 and d.range === range 0→1 must ARRIVE; ⭐ resolvesOnlyThroughRootManifest 2→2, Neither of those is a pass 1→1, boundFailures 11→11 must STAY. ⭐ The first must-STAY leg is the load-bearing one — the cheap wrong fix for this card is to weaken the root bound so the templates stop being refused, and that leg pins the rule still exists and is still wired. ⚠️ errata 62b: ⛔ re-confirm against the ACTUAL merge parent at landing.


Generated by Claude Code

Merged via the queue into main with commit f080538 Sep 12, 2026
37 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-8397-emit-census-reader-manifest branch September 12, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(devx): the emit census's root bound refuses 3 of create-plugin's templates for specifiers the generator's OWN emitted package.json declares

2 participants