Repository navigation
fix(devx): name a remedy a peer's reader can perform in the root bound's refusal - #8531
Conversation
…d's refusal
The root bound refuses a covered block that imports a specifier resolving only
through the repository ROOT's manifest. The rule is deliberate and unchanged
here: the third-party map is built from `dependencies` alone, never from
`peerDependencies` or `devDependencies`, and it fails closed on purpose.
What changes is the message the refusal prints, which named two remedies and
neither fits the case it actually fires on. Four packages (`app-shell`,
`layout`, `plugin-designer`, `plugin-detail`) declare `react-router-dom` as a
peer and none as a dependency, so on those pages:
- "Import what an imported package DECLARES" reads as impossible, because the
package does declare it — in a field this map does not read. Parsing the
sentence correctly required already knowing that.
- "declare the block a fragment" stops the WHOLE block compiling, including
the documented package's own surface. On `packages/layout/README.md` that
surrenders the AppShell / SidebarNav composition, which is exactly where
objectui#3999 and objectui#4817 both landed defects.
A third shape works and neither the message nor the header named it: stand the
peer's bindings in with `declare const` typed to what the block uses them as,
and import only what the package declares in its `dependencies`. The block
still compiles, so the documented composition stays genuinely checked. It is
already worked in `packages/layout/README.md` under "Usage with React Router".
So the refusal now qualifies "declares" with the field it means, names the
stand-in shape with the property that earns it, and ranks the fragment remedy
last with its cost stated. The same remedy is added to the `dependencies` only
header bullet, so rule and message cannot drift apart.
Nothing in the repository pinned a word of this diagnostic, which is how it
drifted; the three remedies and their order are now pinned by content in
`scripts/__tests__/check-doc-snippet-types.test.ts`.
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Two assertions in the new pin matched near-vacuous substrings ("stand ") that a
rewrite could keep by accident. Both now anchor on the full clause they mean.
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
|
LANDED — Landing probe, on a re-fetched
|
| probe | expect | read |
|---|---|---|
a peer is a requirement ON the reader in the gate |
> 0 | 1 |
the peer's bindings in with `declare const` in the gate |
> 0 | 1 |
Declaring the block a fragment is the LAST resort in the gate |
> 0 | 1 |
the refusal names a remedy a reader of a PEER can actually perform (objectui#8059) in the test |
> 0 | 1 |
⭐ firing control — resolves ONLY through the repository root, a phrase present before and after, same command shape |
> 0 | 1 |
negative — the old package DECLARES, or declare the block a fragment |
0 | 0 |
⭐ nonsense control — zzq-not-a-real-token-8059 |
0 | 0 |
⇒ The two zeros are readings, ⛔ not a broken pattern: the same command shape returns 1 on the firing control in the same run, and returns 0 on a token that cannot exist.
What is now on main
The root bound's refusal names three remedies instead of two, ranked, with the cost of the last one stated; the header bullet carries the same remedy so rule and message cannot drift apart; and the whole diagnostic is pinned by content for the first time — nothing in the repository asserted a word of it before this PR, which is how it came to name a remedy a peer's reader cannot perform.
⛔ The dependencies-only rule is unchanged by a byte: no predicate, no manifest field and no enforcement point is touched, and the licensed fence-scanning region [956, 1173) was not entered.
'stand ') that a rewrite could have kept by accident, and both now anchor on the full clause. ⭐ That is the pin catching its own weakness before the tree did.
Generated by Claude Code
Fixes #8059
Not a rule change — the rule is ratified unchanged
scripts/check-doc-snippet-types.mjsmaps third-party specifiers fromdependenciesonly, never
peerDependencies, neverdevDependencies. That is deliberate, documentedin the file's own header, and fails closed; triage ratified it unchanged and this PR does
not widen it by a byte. The
resolvesOnlyThroughRootManifestpredicate, the two enforcementpoints and the four self-controls are untouched.
What changes is the text the refusal prints.
The defect
Four packages declare
react-router-domas a peer and none as a dependency — re-measured onthis PR's base
f76f43628, not taken from the card's58cd01a38:packages/app-shell^6.0.0 || ^7.0.0packages/layout^6.0.0 || ^7.0.0packages/plugin-designer^6.0.0 || ^7.0.0packages/plugin-detail^6.0.0 || ^7.0.0The repository root declares it in
devDependencies(^7.18.2), so the root bound refuses anycovered block importing it. On those four pages the refusal named two remedies and neither
fits the case it actually fires on:
does declare it — in a field this map does not read. Parsing the sentence correctly
required already knowing that, which is knowledge the message itself withheld.
package's own surface included. On
packages/layout/README.mdthat surrenders theAppShell/SidebarNavcomposition — which is exactly where objectui#3999 andobjectui#4817 both landed defects.
A refusal is a contract with its reader. One whose named remedy cannot be performed, and whose
fallback deletes coverage the reader wanted, is the declared-but-not-enforced shape wearing a
message's clothes.
The third shape, which neither the message nor the header named
Stand the peer's bindings in with
declare consttyped to what the block uses them as, andimport only what the package declares in its
dependencies. The block still compiles, so thedocumented composition around it stays genuinely checked. It is already worked in-tree, at
packages/layout/README.mdunder "Usage with React Router".So the refusal now:
deletes coverage.
The same remedy is added to the
dependenciesonly header bullet, so rule and message cannotdrift apart.
Non-vacuity — the message is shown PRINTED, not merely present in the source
A message change can assert nothing, so the failing case was constructed: the
declare conststand-ins in
packages/layout/README.mdwere replaced by a realimport { BrowserRouter, Routes, Route } from 'react-router-dom';. The mutation was proved ondisk by blob hash and by anchored marker counts in both directions before the gate ran, and
restored under an
EXIT INT TERMtrap; the restore is proved by state (git diff HEADempty,hash back to the HEAD blob), never by an exit code.
Before (
node scripts/check-doc-snippet-types.mjs, exit 1):After (same construction, same exit 1):
Both runs printed
Root bound: 1 block(s) ... and were NOT semantically checked.Nothing pinned a word of this diagnostic — which is how it drifted
git grepover the whole tree found the two remedy sentences in one place: the gate sourceitself. No test asserted any of it. A diagnostic nothing pins drifts silently, so this PR adds
the pin, in the
ROOT BOUND ... in this repositoryblock that already pins the rule's header.Reverse verification, from the committed state: restoring the OLD remedy sentence in the gate
(mutation proved on disk by hash and marker counts,
node --checkon the ablated source,restore proved by
git diff HEADempty) turns the new pin red —AssertionError: expected '#!/usr/bin/env node...' to contain 'a peer is a requirement ON the reader',Tests 1 failed | 100 passed. Predicted direction was "turns red"; that is what was observed.The fence, re-derived rather than taken
The bound message is inside the region after the
── Fence scanningbanner. That region islicensed by
scripts/__tests__/check-doc-example-types.test.ts:373, and the licence ischeck-doc-example-types' own card's, not this one's. Re-derived on this base::945, the bound message at:2623(post-change), the header bullet at:341— before the banner, outside the region entirely;export function scanFences(source)and
export function compileSnippets(. This diff touches neither signature;createHash0 hits,sha2560 hits. Control onthe same file so the zeros are readings:
check-doc-snippet-types7 hits,describe(18 hits;scripts/__tests__/check-doc-example-types.test.tspasses, 46/46, including that licence pin.Verification
Base
f76f43628. All exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?), and thegate verdicts quoted from the gates' own printed lines.
pnpm check:doc-snippets— exit 0 before and after: "Every covered documentation snippetcompiles against the built types." 244 documents scanned, 632 of 632 blocks judged, 0 failed.
node scripts/check-doc-snippet-types.mjs --build-filterexit 0,--emit-censusexit 0 (thetwo other modes
doc-snippet-types.ymlruns).pnpm check:doc-examplesexit 0,pnpm check:doc-example-readersexit 0 — the sibling gatesthat import this harness.
scripts/__tests__/in full: 120 of 121 files pass, 3630 tests. The one failure ischeck-lint-rule-coverage.test.ts, and it is not this diff's: it walks the filesystemfiltered by eslint ignores rather than by git, so the untracked build output
apps/console/plugin.js(produced by this workspace's ownpnpm build, gitignored atapps/console/.gitignore:16) reads as an unledgered file. Moving that artifact aside turnsthe same file green 27/27 with no other change. Already filed as objectui#8369, which is open.
node scripts/check-changeset-presence.mjs— run, not guessed: exit 0, "No source orpublished contract of a released package changed in this range, so no changeset is owed."
2 files changed, 0 of them published source. No
skip-changesetlabel applied: in this repothat label exempts nothing, and the gate's verdict line is the authority.
node scripts/check-governed-queue-guard.mjs --teston both paths — exit 0,"NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched."
pnpm exec turbo ls --affectedagainst the base: 0 packages. The diff is entirely underscripts/, so the owed suite is the tooling one above, not a package'stest/typecheck.pnpm exec eslint . --no-inline-config --format json, run atthe final commit
139b6a001on a clean tree — no narrowing, so no narrowing needs excusing.Population read from eslint's own config: 4503 files. Both files this PR touches are in
that population at 0 errors, 0 warnings. The repository's 94 errors / 12430 warnings are
carried by 78 other files this diff does not touch.
grep -naPfor the C0/C1 set over both touched files returns nothing (exit 1),with a control term present in the same files to prove the read.
Scope
Two files, both under
scripts/. Deliberately not touched, and why:dependencies-only mapping itself — ratified unchanged by triage, and widening it is whatthis card explicitly is not;
scripts/check-doc-example-types.mjs:1289, whose[bound]line is a ledger reason string ina gate with no fragment-marker remedy at all — a different remedy model, not the same defect;
[bound]line, which fires today onreact-router-dominpackages/cli/src/utils/app-generator.ts. That path is report-only and objectui#7864 rulesthat its disposition is decided on a follow-up card and not here.
Drafted by the
domain:devx @ objectuios-dev seat, sessionsession_01FhBNJcLRZLe8M87VcUgpKr,on base
f76f43628.Generated by Claude Code