Skip to content

feat(scripts): bound snippet resolution at the repository root manifest - #7554

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-7463-root-devdep-bound
Sep 3, 2026
Merged

os-steve merged 2 commits into
mainfrom
claude/issue-7463-root-devdep-bound

Conversation

@os-steve

@os-steve os-steve commented Sep 3, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #7463 — its item 2, the second flight, executing the maintainer ruling of 2026-09-03 (objectstack#14909 item 1, option A: the bound lives in the shared harness, unconditionally, for both consumers).

What resolved before this change, and no longer does

The harness maps two things: every workspace package's own exports/types, and every specifier the imported packages DECLARE in their dependencies. Neither says anything about the repository's own package.json — and under pnpm the root's own set is symlinked into /node_modules, one directory above where every block is compiled. So a bare specifier neither map covered still resolved, as long as this repository happened to declare it. The inherited UNDECLARED control never reached that: it bounds TRANSITIVE packages, which pnpm leaves only under .pnpm/.

compileSnippets now refuses such a specifier and keeps the block importing it OUT of the semantic program, the way an unparseable block is kept out — so the coverage count states what was judged rather than counting it as checked.

One predicate (resolvesOnlyThroughRootManifest), two enforcement points, because a bound has to be both total and attributable:

  • the resolver (host.resolveModuleNames, scoped to the virtual probe directory — the rule is about what a SNIPPET may import, never about how a declaration file inside node_modules resolves its own imports). This is the regime, and the new ROOT-DECLARED control is the probe that it is live.
  • per block, from the parsed AST — never a regex. This is the attribution. Measured while deriving the bound: a regex over the same corpus read npm install project-name inside a README example's template literal as an import of project-name, and a false refusal reds a document that is correct.

⛔ Not a parameter defaulting to off: two resolution regimes behind one function would answer "did resolution stay narrow?" with "depends who is asking".

One exemption, about authorship rather than resolution: the JSX factory module. Every block is compiled as TSX, so the compiler imports react/jsx-runtime on the author's behalf. Measured: react is mapped in the docs corpus only because @object-ui/layout declares it as a real dependency, while the skills corpus imports no package that does — so without the exemption the same JSX tag would be bounded in one gate and not the other, over a line nobody wrote. Exempted at both enforcement points, so they cannot disagree about one specifier.

The two known specifiers, before and after

Measured on this branch at 58737af, on a tree built from the gate's own --build-filter closure; every reading re-run unchanged at the merged head 3ca73aa (see the merge section below).

reading before after
skills gate, Unmapped specifiers @playwright/test, vitest @playwright/test, vitest (the line stays, now as the report of what the bound refuses)
skills gate, marked ts fences JUDGED 17 of 17 13 of 17 — 4 refused, all declared
docs gate, blocks compiled / judged 456 / 456, 0 failed 455 / 455, 0 failed
docs gate, blocks the bound refuses on the UNFIXED corpus n/a exactly 1
skills --measure, ts candidates passing 20 / 121 14 / 121
docs gate --build-filter byte-identical to the pre-change run, so the workflow's build step is unmoved

The one docs block, re-measured at this branch point and matching the ruling's premise exactly:

[bound]  content/docs/guide/objectos-integration.mdx:638  imports '@playwright/test'

Nothing else in the 456-block docs corpus and nothing outside skills/objectui/guides/testing.md in the skills corpus is touched.

The one docs snippet: declared, and the guide now names the install

content/docs/guide/objectos-integration.mdx, the "Integration Tests" fence, is DECLARED through this gate's own mechanism (an MDX-expression fragment marker carrying a written reason and the measured diagnostic, the idiom the same file already uses ten times over) — and, taking the ruling's stated preference for the form a reader can reproduce, the section now tells the reader what to install before the fence:

End-to-end tests drive the running app, so they need a browser runner the app itself does not depend on. Install it in your project first — npm install -D @playwright/test … and run these specs with npx playwright test.

The example is kept intact; nothing was deleted to make a gate green.

The four marked skill fences, and why they are declared in the SCRIPT

The bound also refuses four MARKED fences in skills/objectui/guides/testing.md (:60, :94, :218 on vitest; :258 on @playwright/test). The ruling names those two specifiers as the marked population it closes but rules only on the docs snippet, so this is the judgement this flight had to make, and it is the one thing here beyond the ruling's letter:

They are carried in a new shrink-only KNOWN_ROOT_DEVDEP_EXAMPLES, mirroring KNOWN_BARE_ANY_EXAMPLES one flight earlier, because:

  • the card's own constraint is zero new red on day one, and every prior item on it landed that way;
  • skills/** is a GOVERNED surface and outside this PR's file surface, so unmarking a fence here is both out of bounds and the "gate weakens the guide to suit itself" move the shrink-only lists exist to prevent;
  • the examples are correct — they are unit and end-to-end tests written against test runners the reader installs, which the guide's first sentence names. What is missing is not correctness but the gate's ability to prove it.

⚠️ A declared row is uncovered debt, never a green, and every line of the run says so: the Root bound line counts refusals, Semantic phase drops to 13 of 17, a NOTE spells it out, and the success sentence itself now reads "…the root bound could reach … 4 declared row(s) … were NOT reached, and this line does not speak for them." Retiring a row means giving the example an import the reader provably has, or the guide's owner deciding the fence should not be gated — a skills judgement, in a skills PR.

Self-tests, both directions

--self-test grew by 10 legs, to 52 cases pass; the two vitest suites grew by 15 and 8 cases, to 67 and 73 tests respectively:

  • a root-devDependency-only specifier IS refused, and the refusal names it;
  • a specifier a documented package DECLARES still resolves, and its block is still judged — the load-bearing negative half, since a bound that refused everything would satisfy the positive half alone;
  • a refused block is kept out of the program rather than counted as judged;
  • an undeclared refusal is red · a declared one is not · a declared row that is no longer refused is STALE;
  • the AST specifier walk finds static, type-only, side-effect, re-export and dynamic imports, and does NOT find an import-shaped line inside a template literal;
  • the JSX factory module is never refused, even with react root-declared and unmapped;
  • the root manifest is read from BOTH dependency fields, not only the populated one;
  • every debt row names a specifier the root really declares and a fence that really exists and really carries the marker;
  • no COVERED docs snippet imports a root-only specifier — the build-free half of the gate's own verdict;
  • the four inherited harness self-controls stay green in every run above.

Reverse verification

Three legs, each with the fix committed FIRST, mutated under a trap restoring ABSOLUTE paths, the mutation proven on disk by grep counts BEFORE any verdict was read, and the restore proven by git hash-object equal to the HEAD blob plus an empty git diff HEAD. No dist/ is involved: both gates run from source, and the packages they resolve against were unchanged.

  • A — the bound is what reds the docs snippet. Removed the new fragment declaration from the mdx (declaration count 1 → 0; blob 3115b38e → a6c30eab): docs gate EXIT 1 with [bound] content/docs/guide/objectos-integration.mdx:644 imports '@playwright/test' and Semantic phase: 455 of 456. Restored to 3115b38e, count back to 1, git diff HEAD empty.
  • B — the bound is applied, not merely described. Replaced the predicate's verdict with a constant false (removed-text 1 → 0, injected-text 0 → 1; blob 99bf34d2 → d959bc4d): both gates EXIT 2 (couldNotRun), the ROOT-DECLARED control reporting produced 0 diagnostic(s) and a specifier only the repository ROOT declares still resolves — the bound is not being applied. That is the whole point of the control: with the bound off the gates refuse to render a verdict instead of going quietly green. Restored to 99bf34d2, git diff HEAD empty.
  • C — the debt list only shrinks, both directions. Deleted the :258 row (row count 1 → 0; blob 8c5c5ec2 → 82e6826d): EXIT 1, [bound] … testing.md:258 and 1 NOT declared. Added a ghost row (testing.md:9999 vitest, ghost count 0 → 1; blob → 617553e3): EXIT 1, [stale-baseline] this row is no longer refused. Restored both times to 8c5c5ec2; git diff HEAD and git status --porcelain both empty.

Gates, at head 3ca73aa (the merge commit)

Exit codes captured by redirect BEFORE any pipe; each verdict quoted from the gate's own printed line.

gate exit its own verdict line
pnpm check:doc-snippets 0 Every covered documentation snippet compiles against the built types.
pnpm check:skill-examples 0 Every marked skill example the root bound could reach holds up against the built types — 4 declared row(s) in KNOWN_ROOT_DEVDEP_EXAMPLES were NOT reached, and this line does not speak for them. + Marked: 17 ts fence(s) (floor 17), 39 json fence(s) (floor 39) — the floor is SHRINK-ONLY.
check-skill-examples.mjs --self-test 0 52 cases pass (… the ROOT BOUND in both directions with its own shrink-only baseline and control …)
check-skill-examples.mjs --measure 0 Starting population — ts: 14/121 pass; json: 39/56 pass. + Root-bound would-be population — 18 refusal(s) over every candidate, of which 4 sit in a MARKED fence.
pnpm check:doc-fences 0 every TypeScript block in 227 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) … (SHRINK-ONLY)
pnpm check:skills-paths 0 check-skills-paths: OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined).
pnpm check:skill-eval-tokens 0 Every must_contain token is taught by its own skill bundle.
pnpm check:doc-types 0 Every documented component type is registered.
pnpm check:control-bytes 0 check-control-bytes: OK (scanned 6224 tracked text file(s); skipped 85 binary).
pnpm check:pre-install-import-graph 0 OK — 23 pre-install step(s) in 18 job(s) run 21 scripts/ gate(s)
check-changeset-presence.mjs 0 No source or published contract of a released package changed in this range, so no changeset is owed.
check-governed-queue-guard.mjs --test over all 5 changed paths 0 NOT GOVERNED — 5 path(s) checked against 5 governed surface(s); none matched.
tsc -p tsconfig.scripts.json --noEmit 0 (no diagnostics)
vitest run over the 11 suites that reference either script 0 Test Files 11 passed (11) · Tests 402 passed (402) (394 before the merge; +8 are objectui#7550's floor pins)
pnpm lint (repo-wide, no narrowing claimed) 0 Tasks: 47 successful, 47 total — 0 errors

NOT MEASURED, stated rather than implied: tsconfig.scripts.json sets allowJs with checkJs FALSE on purpose, so errors inside the two .mjs BODIES are not reported — --listFiles confirms both modules and both edited test files are in the program, which makes the clean tsc a statement about the callers, not about the module bodies. CI convergence is likewise not measured here; this report is filed at draft-PR time per the dispatch contract.

Merge with objectui#7550's floors (3ca73aa)

PR #7553 (objectui#7550, the per-category MARKED_FLOOR ratchet) landed on the same two files while this PR was in draft, and GitHub read this branch mergeable_state: dirty. Merged origin/main at 34ea56d into the branch — a merge commit, no rebase, no force. Both behaviours are kept whole:

  • scripts/check-skill-examples.mjs auto-merged. The summary carries both: Marked: 17 ts fence(s) (floor 17), 39 json fence(s) (floor 39) — the floor is SHRINK-ONLY. on the floors' line, and Root bound: 4 fence(s) refused … 4 row(s) declared … 0 NOT declared, 0 declared row(s) no longer refused. on this PR's. The exit path reads both — the floor-breach check and the undeclared/stale root-bound rows.
  • One conflict, in scripts/__tests__/check-skill-examples.test.ts: the import list, where the two PRs each added symbols. Resolved by keeping both sides (KNOWN_ROOT_DEVDEP_EXAMPLES and MARKED_FLOOR, rootDevDepRowKey and reconcileFloors). No test from either side was dropped: the merged suite runs 73 + 8 cases and the union is green.
  • The two docblocks now name each other — the interaction neither PR could state alone, and the one a reader needs. MARKED_FLOOR counts MARKS, not verifications: a fence the root bound refuses keeps its marker and stays inside the floor while being type-checked by nothing, so the floor's number is not coverage and the Root bound / Semantic phase lines are what say how many marked fences were reached. And unmarking a fence to silence a root-bound refusal would breach that floor — two ratchets, counting different things, neither substituting for the other.

Re-run at the merged head 3ca73aa, all green: pnpm check:skill-examples, pnpm check:doc-snippets, check-skill-examples.mjs --self-test (52 cases pass), pnpm check:doc-fences, pnpm check:skills-paths, pnpm check:skill-eval-tokens, pnpm check:control-bytes, check-changeset-presence.mjs (no changeset is owed, now compared against 34ea56d), check-governed-queue-guard.mjs --test (NOT GOVERNED — 5 path(s)), tsc -p tsconfig.scripts.json --noEmit, the 11-suite vitest union (402 passed), and repo-wide pnpm lint (Tasks: 47 successful, 47 total, 0 errors).

Session for this and every earlier revision: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox.

Posture

Not governed (verdict above), no changeset owed (verdict above, and no skip-changeset label is applied — that label is a phantom in this repository). Draft, for the seat to review and land.

A bare specifier that neither the workspace map nor the declared-dependency
map covers still resolved in the shared snippet harness, because pnpm
symlinks the repository ROOT's own devDependencies into `/node_modules`, one
directory above where every block is compiled. Measured: `vitest` and
`@playwright/test`. A guide could import a test runner its reader was never
told to install and stay green on this workspace's copy.

`compileSnippets` now refuses such a specifier, unconditionally for both
consumers, and keeps the block importing it out of the semantic program the
way an unparseable block is kept out — so the coverage count says what was
judged instead of counting it as checked. One predicate, two enforcement
points: the resolver (the regime, proven live on every run by a new
ROOT-DECLARED control) and a per-block AST walk (the attribution). The JSX
factory module is the single exemption, because the compiler imports it on
the author's behalf.

The one docs snippet the bound reds is declared, with the guide now naming
the install the reader needs. The four marked skill fences it reds are
carried in a shrink-only `KNOWN_ROOT_DEVDEP_EXAMPLES`, so the bound lands
with zero new red; retiring a row is a skills judgement over a governed
surface, not this change's to make.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
objectui#7550's per-category `MARKED_FLOOR` ratchet (PR #7553) landed on the
same two files. Both behaviours are kept whole:

- `scripts/check-skill-examples.mjs` auto-merged. The summary now carries the
  floors on its `Marked:` line and the root bound's refused count on its own
  `Root bound:` line, and the exit path reads both — the floor breach check and
  the undeclared/stale root-bound rows.
- The one conflict was this suite's import list, resolved by keeping both
  sides' symbols.
- The two docblocks now name each other, which is the interaction neither PR
  could state alone: the floor counts MARKS, so a fence the root bound refuses
  stays inside it while being type-checked by nothing, and unmarking one to
  silence a refusal would breach the floor. Two ratchets, counting different
  things, neither substituting for the other.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@os-steve
os-steve added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 28b3cc3 Sep 3, 2026
30 checks passed
@os-steve
os-steve deleted the claude/issue-7463-root-devdep-bound branch September 3, 2026 22:31
os-steve pushed a commit that referenced this pull request Sep 3, 2026
…es (#7557)

The root-manifest bound (#7554) refused four MARKED fences in
skills/objectui/guides/testing.md and carried them as shrink-only debt in
KNOWN_ROOT_DEVDEP_EXAMPLES. A declared row is uncovered debt, not a green:
every run printed "13 of 17 ts fence(s) judged".

Measured first, per the card's two options. check-skill-examples.mjs reads
exactly ONE declaration on a fence -- MARKER, an exact-string match on the
whole line, with no reason field, no install field and no capture group --
and check-doc-snippet-types.mjs's FRAGMENT_MARKER is not read by it at all
(the word appears twice in the file, both times in prose about the docs
gate's opposite opt-OUT design, never in code). The bound itself,
resolvesOnlyThroughRootManifest, is a pure function of the specifier and two
repository-wide maps with no per-block parameter to declare into. So there is
no form in which a fence keeps its mark and names its install, and even the
docs gate's fragment form routes a block AWAY from the compiled set -- the
same coverage loss as the row, under a different name. That selects option 2.

- unmark the four fences, reason written beside each: vitest and
  @playwright/test are the reader's own installs, not packages this
  repository publishes;
- delete all four rows, leaving KNOWN_ROOT_DEVDEP_EXAMPLES empty, with its
  docblock recording the measurement and when a NEW row is still right;
- lower MARKED_FLOOR ts 17 -> 13, reason beside the row, in this same PR --
  the move #7550's ratchet allows only here.

Not a precedent for unmarking a red fence: these four were type-checked by
nothing at all for the whole of their life as rows.

Before: Root bound 4 refused, 4 rows declared; Semantic phase 13 of 17.
After:  Root bound refuses nothing; Semantic phase 13 of 13; Marked 13 ts
        (floor 13), 39 json (floor 39).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
…es (objectstack-ai#7557) (objectstack-ai#7568)

The root-manifest bound (objectstack-ai#7554) refused four MARKED fences in
skills/objectui/guides/testing.md and carried them as shrink-only debt in
KNOWN_ROOT_DEVDEP_EXAMPLES. A declared row is uncovered debt, not a green:
every run printed "13 of 17 ts fence(s) judged".

Measured first, per the card's two options. check-skill-examples.mjs reads
exactly ONE declaration on a fence -- MARKER, an exact-string match on the
whole line, with no reason field, no install field and no capture group --
and check-doc-snippet-types.mjs's FRAGMENT_MARKER is not read by it at all
(the word appears twice in the file, both times in prose about the docs
gate's opposite opt-OUT design, never in code). The bound itself,
resolvesOnlyThroughRootManifest, is a pure function of the specifier and two
repository-wide maps with no per-block parameter to declare into. So there is
no form in which a fence keeps its mark and names its install, and even the
docs gate's fragment form routes a block AWAY from the compiled set -- the
same coverage loss as the row, under a different name. That selects option 2.

- unmark the four fences, reason written beside each: vitest and
  @playwright/test are the reader's own installs, not packages this
  repository publishes;
- delete all four rows, leaving KNOWN_ROOT_DEVDEP_EXAMPLES empty, with its
  docblock recording the measurement and when a NEW row is still right;
- lower MARKED_FLOOR ts 17 -> 13, reason beside the row, in this same PR --
  the move objectstack-ai#7550's ratchet allows only here.

Not a precedent for unmarking a red fence: these four were type-checked by
nothing at all for the whole of their life as rows.

Before: Root bound 4 refused, 4 rows declared; Semantic phase 13 of 17.
After:  Root bound refuses nothing; Semantic phase 13 of 13; Marked 13 ts
        (floor 13), 39 json (floor 39).


Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants