Skip to content

fix(devx): name a remedy a peer's reader can perform in the root bound's refusal - #8531

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-8059-bound-remedy-text
Sep 8, 2026
Merged

baozhoutao merged 2 commits into
mainfrom
claude/issue-8059-bound-remedy-text

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #8059

Not a rule change — the rule is ratified unchanged

scripts/check-doc-snippet-types.mjs maps third-party specifiers from dependencies
only, never peerDependencies, never devDependencies. That is deliberate, documented
in the file's own header, and fails closed; triage ratified it unchanged and this PR does
not widen it by a byte. The resolvesOnlyThroughRootManifest predicate, the two enforcement
points and the four self-controls are untouched.

What changes is the text the refusal prints.

The defect

Four packages declare react-router-dom as a peer and none as a dependency — re-measured on
this PR's base f76f43628, not taken from the card's 58cd01a38:

package peerDependencies in dependencies
packages/app-shell ^6.0.0 || ^7.0.0 no
packages/layout ^6.0.0 || ^7.0.0 no
packages/plugin-designer ^6.0.0 || ^7.0.0 no
packages/plugin-detail ^6.0.0 || ^7.0.0 no

The repository root declares it in devDependencies (^7.18.2), so the root bound refuses any
covered block importing it. On those four pages the refusal named two remedies and neither
fits the case it actually fires on
:

  1. "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, which is knowledge the message itself withheld.
  2. "declare the block a fragment" stops the whole block compiling, the documented
    package's own surface included. On packages/layout/README.md that surrenders the
    AppShell / SidebarNav composition — which is exactly where objectui#3999 and
    objectui#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 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 around it stays genuinely checked. It is already worked in-tree, at
packages/layout/README.md under "Usage with React Router".

So the refusal now:

  • qualifies "declares" with the field it means, and says why a peer is not that field;
  • names the stand-in shape together with the property that earns it (the block still compiles);
  • keeps the fragment remedy but ranks it last, with its cost stated — it is the one that
    deletes coverage.

The same remedy is added to the dependencies only header bullet, so rule and message cannot
drift 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 const
stand-ins in packages/layout/README.md were replaced by a real
import { BrowserRouter, Routes, Route } from 'react-router-dom';. The mutation was proved on
disk by blob hash and by anchored marker counts in both directions before the gate ran, and
restored under an EXIT INT TERM trap; the restore is proved by state (git diff HEAD empty,
hash back to the HEAD blob), never by an exit code.

Before (node scripts/check-doc-snippet-types.mjs, exit 1):

  [bound]     packages/layout/README.md:201  imports 'react-router-dom', which resolve only
through this repository's ROOT package.json — this workspace's own devDependency set, not
anything a reader of the documented packages installs. Import what an imported package
DECLARES, or declare the block a fragment with a reason naming what the reader must install:
                (the fragment marker example)

After (same construction, same exit 1):

  [bound]     packages/layout/README.md:201  imports 'react-router-dom', which resolve only
through this repository's ROOT package.json — this workspace's own devDependency set, not
anything a reader of the documented packages installs. Import what an imported package
declares in its `dependencies`. A `peerDependencies` entry is a declaration too, but not one
this map reads: a peer is a requirement ON the reader, which may be unmet.
                If the specifier IS such a peer and this block needs its bindings, stand them
in with `declare const` typed to what the block uses them as, and import only what the package
declares. The block still compiles, so the documented package's own surface around it stays
checked — worked example: `packages/layout/README.md`, under "Usage with React Router".
                Declaring the block a fragment is the LAST resort: it stops the WHOLE block
compiling, that package's own surface included, so reach for it only when the block cannot
compile for reasons a stand-in cannot supply. Its reason must name what the reader installs:
                (the fragment marker example)

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 grep over the whole tree found the two remedy sentences in one place: the gate source
itself. No test asserted any of it. A diagnostic nothing pins drifts silently, so this PR adds
the pin, in the ROOT BOUND ... in this repository block 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 --check on the ablated source,
restore proved by git diff HEAD empty) 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 scanning banner. That region is
licensed by scripts/__tests__/check-doc-example-types.test.ts:373, and the licence is
check-doc-example-types' own card's, not this one's. Re-derived on this base:

  • the banner is at :945, the bound message at :2623 (post-change), the header bullet at
    :341 — before the banner, outside the region entirely;
  • the pin asserts exactly two substrings after the banner, export function scanFences(source)
    and export function compileSnippets(. This diff touches neither signature;
  • no test in the repository hashes this gate: createHash 0 hits, sha256 0 hits. Control on
    the same file so the zeros are readings: check-doc-snippet-types 7 hits, describe( 18 hits;
  • scripts/__tests__/check-doc-example-types.test.ts passes, 46/46, including that licence pin.

Verification

Base f76f43628. All exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?), and the
gate verdicts quoted from the gates' own printed lines.

  • pnpm check:doc-snippets — exit 0 before and after: "Every covered documentation snippet
    compiles against the built types."
    244 documents scanned, 632 of 632 blocks judged, 0 failed.
  • node scripts/check-doc-snippet-types.mjs --build-filter exit 0, --emit-census exit 0 (the
    two other modes doc-snippet-types.yml runs).
  • pnpm check:doc-examples exit 0, pnpm check:doc-example-readers exit 0 — the sibling gates
    that import this harness.
  • scripts/__tests__/ in full: 120 of 121 files pass, 3630 tests. The one failure is
    check-lint-rule-coverage.test.ts, and it is not this diff's: it walks the filesystem
    filtered by eslint ignores rather than by git, so the untracked build output
    apps/console/plugin.js (produced by this workspace's own pnpm build, gitignored at
    apps/console/.gitignore:16) reads as an unledgered file. Moving that artifact aside turns
    the 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 or
    published contract of a released package changed in this range, so no changeset is owed."

    2 files changed, 0 of them published source. No skip-changeset label applied: in this repo
    that label exempts nothing, and the gate's verdict line is the authority.
  • node scripts/check-governed-queue-guard.mjs --test on both paths — exit 0,
    "NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched."
  • pnpm exec turbo ls --affected against the base: 0 packages. The diff is entirely under
    scripts/, so the owed suite is the tooling one above, not a package's test/typecheck.
  • Lint: the whole-repo union pnpm exec eslint . --no-inline-config --format json, run at
    the final commit 139b6a001 on 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.
  • Control bytes: grep -naP for 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:

  • the dependencies-only mapping itself — ratified unchanged by triage, and widening it is what
    this card explicitly is not;
  • scripts/check-doc-example-types.mjs:1289, whose [bound] line is a ledger reason string in
    a gate with no fragment-marker remedy at all — a different remedy model, not the same defect;
  • the emitted-code census's own [bound] line, which fires today on react-router-dom in
    packages/cli/src/utils/app-generator.ts. That path is report-only and objectui#7864 rules
    that its disposition is decided on a follow-up card and not here.

Drafted by the domain:devx @ objectui os-dev seat, session session_01FhBNJcLRZLe8M87VcUgpKr,
on base f76f43628.


Generated by Claude Code

…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
@github-actions github-actions Bot added the tests label Sep 8, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 8, 2026 07:18
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 3bc187b Sep 8, 2026
31 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-8059-bound-remedy-text branch September 8, 2026 07:35
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

LANDED — 3bc187b7f on origin/main. domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T07:34Z. PR #8531 (card objectui#8059), queued 07:19:08Z, merged first pass — ⛔ no dequeue.

Landing probe, on a re-fetched origin/main (3bc187b7f16e547c781f801d235de1d43fe3c91e)

⛔ Not the shared checkout's working tree; every row is git grep -c -F … origin/main -- <path>.

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.

⚠️ Recorded from the merged commit, ⛔ not from the report: the dev tightened its own pin before landing — two assertions had matched near-vacuous substrings ('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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants