Uh oh!
There was an error while loading. Please reload this page.
fix(spec): give the client-SDK surface a page-scoped docs root so SDK pages can opt into check:skill-examples - #12339
Merged
Conversation
`check:skill-examples` resolves modules per SURFACE, and a root was always a whole subtree — so "which packages may this page import?" was a property of the tree a page lives in. `content/docs/**` resolves against `@objectstack/spec` alone, and spec does not depend on `@objectstack/client`, so a marker on any docs fence importing the SDK red with TS2307 on correct code. Two pages recorded that in prose and left their SDK blocks deliberately unmarked. `SourceRoot.pages` makes a root a page SET rather than a tree, so the two SDK pages join the client-SDK surface that already resolves those packages for its 19 TSDoc blocks — no new resolution environment and no new extraction code. The scoped root's `pages` and the broad root's `excludePages` read one shared constant, and two guards prove the partition every run: no file may be scanned by two surfaces, and every scoped page must exist. Opts in the four fences that are self-contained (Quick Start, the `createFilter()` chain and the React Hooks block on the SDK page; the `services.data` example on the data-service page). The remaining nine are continuation fragments that read Quick Start's `client` and stay unmarked; both pages' notes now record the measurement rather than the old constraint.
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
`assertScopedPagesExist()` asserted SDK_DOCS_PAGES unconditionally, but this script is run against repo-SHAPED sandbox trees as well as the repo: the #7181 dist-freshness pins build one that seeds skills/, packages/spec/src and the two client packages and has no content/docs at all. There the guard reported both SDK pages missing and — being an assert that runs before everything else — spoke ahead of the three verdicts those tests pin, hijacking a positive control, a staleness refusal and an orphan-marker finding alike. `sourceFiles()` already skips a root whose `dir` does not exist. The defect was two predicates disagreeing about whether a root is present in this checkout at all, the same shape as the two fence closers (#11690) and the two fence-ownership notions (#11355) this file has already collapsed. The guard now restates that one rule. The rename protection is untouched: content/docs always exists in a real checkout, so a page renamed inside it is still judged and still reds. Pinned in both directions, with a control — an absent dir is silent in BOTH predicates, while the identical page list under a dir that DOES exist is still flagged, so the exemption is the dir and not a predicate gone quiet.
os-litant
marked this pull request as ready for review
August 25, 2026 20:37
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#12048
What was wrong
check:skill-examplesresolves modules per surface, and aSourceRootwas always a whole subtree — so "which packages may this page import?" was a property of the tree a page happens to live in.content/docs/**belongs to theskills + docssurface, whosepathsmap derives from@objectstack/specalone, and spec does not depend on@objectstack/client. A marker on any docs fence importing the SDK therefore red withTS2307: Cannot find module '@objectstack/client'— on correct code. Two pages recorded that constraint in prose and left their SDK blocks deliberately unmarked, so the most SDK-dense page in the docs was structurally unverifiable while #8140's narrowing of 51 client return types falsified two of its examples with CI silent throughout.Option C, as priced on the card
The spec seat adopted C — a docs root scoped to a page set, on the existing client-SDK surface that already resolves
@objectstack/client,@objectstack/client-reactand react's real types for its 19 TSDoc blocks. No new resolution environment, no new extraction code. A was rejected (adding the SDK to the spec surface'sselfPackagesinverts the dependency direction that surface exists to model, and would let any docs page silently acquire the SDK); B was the shipped interim and is now retired by this change.The card named the one genuine design cost honestly:
SourceRoot.diris a directory, so per-page scoping had to be built. It isSourceRoot.pages— an explicit file list, relative todir, instead of a walk. The fork-back condition did not trigger: this is a scoped root plus a filter on an existing surface, and the file/marker/tsc pipeline is untouched.Two roots over one tree brings its own failure mode, so both halves are asserted rather than trusted:
pagesand the broad root'sexcludePagesread one shared constant (SDK_DOCS_PAGES).assertDisjointSourceFiles()proves per run that no file is scanned by two surfaces — an invariant that held by accident while every root owned its own tree, and whose violation means one page compiled twice in two environments, with the failing verdict unfixable from the page.assertScopedPagesExist()makes that a hard error.This builds on #12308's shape rather than around it: the REFUSE verdict and the unmarked-population sweep are untouched, and REFUSE correctly named the new
docs-sdkroot during the measurement below.Which fences were opted in, and which were not
All 13 fences on
api/client-sdk.mdxand both ondata-service.mdxwere marked, the gate run, and the tree reverted. Readings per fence (source lines are pre-marking):client(2/13/10/3/5/69 diagnostics)createQueryclient(1)createFilterunknowncatch bindingClientConfigLogger(1)services.dataexample114 diagnostics with all 13+1 marked, and zero TS2307. The pre-change sweep recorded on the card produced 128 including TS2307 on every SDK import. The nine that stay unmarked are the continuation class the page's own note documents: Quick Start establishes
clientonce and each later block reads it. Making those compile would mean injecting casts into prose whose subject IS the real API — teaching worse code than the page teaches now. Both pages' notes are rewritten to record the measurement instead of the retired constraint.Verification
Every reading below is the gate's own verdict line, at head
31c166e4; the tree was confirmed pristine (git status --porcelainempty) after each reverted measurement.Fix in place —
pnpm --filter @objectstack/spec check:skill-examples:256 → 260 marked blocks; the client-SDK surface 19 → 23. The
skills + docscount is unchanged at 227, which is the carve-out being a partition rather than a subtraction.Reverse verification (control). With the four markers left on disk and
packages/spec/scripts/check-skill-examples.tsrestored tob8419bd5a— mutation confirmed on disk before the run (SDK_DOCS_PAGESoccurrences 0, markers still 3+1) — the run reds, and the direction is the card's:13 diagnostics: 5× TS2307, plus TS7026/TS2875/TS7006 from the React block meeting a surface with no react types.
524:30is thecreateFilterimport — the same statement and the same column as the card's485:30, at its post-#12045 line. Restored withgit checkout HEAD -- packages/spec/scripts/check-skill-examples.ts; porcelain clean.Self-test (
--self-test, run as the first half of the gate script) gains page-scope fixtures, including the load-bearing control: an un-partitioned broad root does reach the carved-out page, so the partition assertions are not passing over an empty corpus. It also pins overlap detection and missing-page detection in both directions, and asserts the realSURFACESsatisfy both invariants today.Gate families. Re-derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(34 families, more than the dispatch named — it addedcheck:doc-security-posture,check:docs-redirects,check:docs-single-h1,check:page-declaration-shape,check:published-readme-links,check:react-page-adapter-contract,check:runtime-services-index,check:doc-formula-expressions,check-doc-frontmatter,check-doc-route-spelling,check-docs-section-name,check-section-landing-index,check-dev-prereqs,check:docs). 33 of 34 exit 0, each exit code captured before any pipe.One declared narrowing:
node scripts/check-dev-prereqs.mjsexits 1 in this worktree with "The workspace is not built — 34 of 67 workspace packages declare an entry point under dist/ that is not on disk", naming@objectstack/account,@objectstack/setup,@objectstack/studioand 31 others. Only the@objectstack/client-reactdependency closure was built here, not the whole workspace. The diff is three files and touches nopackage.json, nodist/, and no build config, so it cannot reach that gate's predicate; CI runs it after "Build workspace packages".Follow-up commit
31c166e4— the guard must skip a root whosediris absentCI
Test Corewent red onpackages/spec/scripts/dist-freshness-adoption.test.ts(the #7181 pin suite for this very script), and it was a true red attributable to this diff.assertScopedPagesExist()assertedSDK_DOCS_PAGESunconditionally, but this script is run against repo-shaped sandbox trees as well as against the repo: that suite builds one seedingskills/,packages/spec/srcand the two client packages, with nocontent/docsat all (measured: zero occurrences of "content" in the whole test file). There the guard reported both SDK pages missing and, being an assert that runs before everything else, spoke ahead of the three verdicts those tests pin — hijacking a positive control, a staleness refusal and an orphan-marker finding alike.sourceFiles()already skips a root whosedirdoes not exist. The defect was two predicates disagreeing about whether a root is present in this checkout at all — the same shape as the two fence closers (#11690) and the two fence-ownership notions (#11355) that this file has already collapsed into one. The guard now restates that one rule.The rename protection is untouched, because it answers a different question:
content/docsalways exists in a real checkout, so a page renamed inside it is still judged and still reds. Absent-dir means "this tree does not carry this corpus", never "this corpus lost a page". Pinned in both directions with a control — an absentdiris silent in both predicates, while the identical page list under adirthat does exist is still flagged, so the exemption is the dir and not a predicate gone quiet.Post-fix readings at
31c166e4:Full
@objectstack/specsuite — the suite the red lived in, run in full this time rather than narrowed:check:skill-exampleson the real corpus is unchanged at 260 marked blocks (227 / 10 / 23), and the 34-family union was re-derived and re-run on this head: 33 of 34 exit 0, with the same single environmentalcheck-dev-prereqsnarrowing declared above. The new self-test fixture was ablated (guard line removed, absence confirmed on disk, restored byte-identically via a trap and verified withgit hash-object) and fails with exactly its own message — it is load-bearing, not decorative.dispatch-gates.mjsreports this branch's tree is 8 commits behindorigin/mainwith 4 derivation inputs changed. Measured rather than shrugged at:lint.yml's change is a pure comment block (#12211's recorded negative result — no gate steps, no globs) anddispatch-gates.mjsis +79/-0 (the staleness warner itself). The derived family list is byte-identical to the pre-merge one, so the union above is not stale in substance.Changeset
skip-changeset— this PR changes a CI gate script and contributor-facing prose notes; no published package's behaviour or surface moves. Same disposition as #12175 and #12308 on this file.Generated by Claude Code