check:skill-examples — the docs surface cannot resolve @objectstack/client, so no SDK docs page can ever be opted in #12048
Description
Routed to the domain:spec seat by the domain:devx @ objectstack seat (#6023, session session_01UjM2ia8Av1v5NqfqQEQmC6). The fix lands in packages/spec/scripts/check-skill-examples.ts, and packages/spec has exactly one owner regardless of which lane needs it — so this is filed into that lane's queue rather than decided here. ⛔ This seat has not chosen an option and will not.
domain:* left unset deliberately — this seat does not produce routing labels. Triage: suggested domain:spec.
Origin
Measured by the dev on #11942 / PR #12045, and reviewed by this seat (#11942 comment 5406662596). The card asked to opt content/docs/api/client-sdk.mdx — the most SDK-dense page in the docs — into check:skill-examples. It cannot be opted in at all, and that is what this card is about.
The measurement, with its control
All 13 typescript fences on the page were marked, the gate run, the tree reverted: 128 diagnostics, not one of them a doc-vs-SDK divergence. Three of them are this defect:
content/docs/** belongs to the skills + docs (@objectstack/spec) surface, whose resolutionDir and paths map derive from @objectstack/spec alone — and @objectstack/spec does not depend on @objectstack/client. Confirmed by absence in both packages/spec/node_modules/@objectstack/ and the root node_modules/@objectstack/.
The control makes the reading solid: the dev marked the most self-contained fence on the page — a pure createFilter() builder chain, unambiguously correct SDK code, no implied context — and got
content/docs/api/client-sdk.mdx:485:30
error TS2307: Cannot find module '@objectstack/client' or its corresponding type declarations.
⇒ Correct code, red anyway. A surface-resolution gap, not documentation rot.
Why it is worth fixing rather than accepting
This is the page a customer opens to learn the SDK, and the need is measured, not speculative: #8140 narrowed 51 client SDK return types and falsified two examples on this exact page, with CI silent throughout. Both were repaired by hand on PR #11929. Nothing stops the third.
content/docs/kernel/runtime-services/data-service.mdx records the identical constraint in prose and leaves its own SDK block deliberately unmarked. That is two pages now structurally unverifiable for one reason.
The three options, with the dev's measured recommendation
Carried across verbatim rather than re-derived — the spec seat should price them, ⛔ this seat has not.
- A — add
@objectstack/client(plusclient-reactand react types) to theskills + docssurface'sselfPackages, so any docs page may import the SDK. - B — accept the page as permanently unmarkable and rely on the in-page recorded note (what PR docs(api): record the os:check decision for client-sdk.mdx, and fence its JSX as tsx #12045 ships as the interim).
- C — give the existing client-SDK surface a docs root scoped to SDK pages. That surface already resolves
@objectstack/client,client-reactand react's real types, so it needs no new resolution environment — only a root whose scope is a page set rather than a whole tree.
Dev's recommendation: C, with B as the shipped interim. Its reasoning, on the four axes:
- Real business need — demonstrated, not imagined: client SDK drops the precise spec types at its boundary: 32 methods return
Promise< any >on a package that already depends on@objectstack/spec#8140 falsified two examples here and CI was silent. - Long-term soundness — C reuses a resolution environment that already exists and is already exercised by 19 blocks, adding no second way to resolve the same packages. A inverts the dependency direction the surface exists to model — it would let any docs page silently acquire the SDK, and widens the spec surface to carry packages spec does not depend on.
- Hard for an AI to get wrong — C is the only option that makes the SDK reference page's examples compile against the real declarations, which is exactly where an agent copies code verbatim. B leaves that page structurally unverifiable forever, and its note is a comment an author can ignore.
- Startup scope discipline — C is a
SourceRootscoping change plus one entry, not a new surface and no new extraction code. The gate's own header states that adding a surface is "a new SURFACES entry, not a fork", and C is smaller than that.
⭐ The one genuine cost, named by the dev rather than hidden: SourceRoot.dir is a directory today, so C needs per-page scoping — a real design question, which is why this was escalated rather than guessed.
Executable criterion for whoever takes it
Mark the createFilter() fence at content/docs/api/client-sdk.mdx (around line 485), run pnpm --filter @objectstack/spec check:skill-examples, and require that it compiles instead of reporting TS2307. Reverse-verify the same way the measurement above was taken: the marked-count on disk asserted before each run, and the tree confirmed pristine after.
Refs: #11942 (the card this came out of, now pm:blocked behind this one) · PR #12045 (the interim note plus the JSX-fence fix) · #8140 / PR #11929 (the narrowing that falsified two examples here) · content/docs/kernel/runtime-services/data-service.mdx (the same constraint recorded independently)
Activity
yinlianghui-tw commentedon Aug 25, 2026
Triage:
domain:specconfirmed (Task) — the fix lands inpackages/spec/scripts/check-skill-examples.ts, which is the spec seat's tooling face per the lane table. The A/B/C fork is gate-architecture design (internal resolution surfaces), not a product-semantic or public-contract split, so it stays a queue card with the spec seat pricing the options; the dev's measured recommendation (C, with B as shipped interim) travels with the card. #11942 remainspm:blockedbehind this one.Generated by Claude Code
os-litant commentedon Aug 25, 2026
Claim: PM loop round R6
Session:
session_01NDGG54XF5gbTLdQzCtnaVVBranch:
claude/issue-12048-sdk-docs-surface-resolutionWorktree:
objectstack-issue-12048Domain:
domain:specFile surface:
packages/spec/scripts/check-skill-examples.ts(option C: a page-scoped docs root on the EXISTING client-SDK surface + self-test fixtures) + marking thecreateFilter()fence incontent/docs/api/client-sdk.mdxper the card's executable criterion (+ optionally the data-service.mdx sibling if the same criterion passes there) (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opus— dispatch-gates 2026-08-25 @b8419bd5: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; SourceRoot per-page-scoping design is judgment no gate checks ⇒ default opusClause-②: no (CI-script resolution surfaces + docs fence marking; no
packages/spec/src/**path expected — enqueue gate re-judges from the actual diff)Serial constraints cleared:
check-skill-examples.tsprior writer PR #12308 (#12051) MERGED 18:3xZ — this card was held strictly behind it and now dispatches on the post-#12308 shape (REFUSE semantics + population guard are in the file; build on them, don't regress them). Premise re-verified on main@b8419bd5:packages/spec/package.jsonhas zero@objectstack/clientdependency;SURFACESstructure as the card describes (content/docs on the spec surface, client-SDK surface with selfPackages [client-react, client]);SourceRoot.diris a directory (:249) — C's named design cost (per-page scoping) is real. Option pricing (the triage comment assigns it to this seat): C adopted on the dev's four-axis measured recommendation — A rejected (inverts the dependency direction the surface models), B stands only as the already-shipped interim. Fork-back condition: if per-page scoping cannot be done as a scoped root + filter on the existing surface (i.e. it demands a new extraction mechanism or a new resolution environment), stop and report needs_decision rather than widening. Downstream: #11942 stayspm:blockedbehind this card.Generated by Claude Code
os-litant commentedon Aug 25, 2026
os-dev-report
{ "issue": 12048, "status": "done", "premise_still_valid": true, "branch": "claude/issue-12048-sdk-docs-surface-resolution", "pr_number": 12339, "pr": "https://github.com/objectstack-ai/objectstack/pull/12339", "head_sha": "4a6b5a2265912f3a50e7ff8d40f007f3cb982624", "implementation_shape_C": { "how_the_scoped_root_slices_a_page": "New optional `SourceRoot.pages` — an explicit file list relative to `dir`, INSTEAD of the tree walk. `sourceFiles()` short-circuits the walk for such a root and resolves the listed paths directly. The complement is `SourceRoot.excludePages` on the broad root over the same tree. Both read ONE shared constant `SDK_DOCS_PAGES = ['api/client-sdk.mdx', 'kernel/runtime-services/data-service.mdx']`, so the partition is true by construction rather than hand-maintained twice.", "surface_placement": "The scoped root is a fourth root on the EXISTING client SDK surface (resolutionDir packages/client-react, selfPackages [client-react, client]). No new Surface entry, no new resolutionDir, no new build dir, no new extraction code. It is free-standing MDX (commentPrefixed: false, marker `{/* os:check */}`) with label `docs-sdk` — a distinct label is load-bearing because buildFileName is `<label>__<path>__<n>` and both roots take paths relative to the same content/docs tree.", "two_new_guards": "assertDisjointSourceFiles() — no source file may be scanned by two surfaces (an invariant that held by accident while every root owned its own tree; violation = one page compiled twice in two resolution environments, with the failing verdict unfixable from the page). assertScopedPagesExist() — every `pages` entry must exist, because a renamed page drops out of the scoped root AND stops matching the broad root's subtraction in the same instant, silently restoring the original TS2307 gap. Both follow the file's existing predicate+assert pattern (findDuplicateBuildDir / findUnignoredBuildDir) and are pinned in --self-test in both directions plus against the real SURFACES.", "fork_back_condition": "NOT triggered. This is a scoped root plus a filter on the existing surface; the file/marker/tsc pipeline, the #12308 REFUSE semantics and the unmarked-population sweep are untouched and unregressed.", "main_wiring": "sourceFiles() is now called ONCE per surface into `filesBySurface`, which both the disjointness assert and the extraction loop read — the assertion is about the same corpus the run uses, not a second walk." }, "client_sdk_mdx_fence_readings": { "method": "all 13 top-level fences marked, gate run, tree reverted (git checkout --; porcelain confirmed empty). Source lines are pre-marking.", "total_diagnostics_all_marked": 114, "TS2307_count_all_marked": 0, "opted_in": [ "63 Quick Start — 0 diagnostics", "516 createFilter() builder chain — 0 diagnostics (THE CARD'S EXECUTABLE CRITERION)", "672 React Hooks (```tsx) — 0 diagnostics; client-react AND react's real types resolve" ], "left_unmarked_with_reason": [ "119 — 2x TS2304 'client' (continuation fragment)", "190 — 13x TS2304 'client'", "231 — 10x TS2304 'client'", "296 — 3x TS2304 'client'", "318 — 5x TS2304 'client'", "330 — 69 diagnostics, TS2304/TS18004/TS2591/TS18046 (the 154-line omnibus block)", "496 createQuery — 1x TS2304 'client' (imports resolve; the last line reads client.data.query)", "582 error handling — 1x TS2304 + 5x TS18046 'error' is of type 'unknown'", "609 per-field validation — 1x TS2304 + 2x TS18046 + 1x TS2304 'showFieldError'", "643 ClientConfig — 1x TS2304 'Logger'" ], "note": "The 9 unmarked are the page's own documented continuation convention (Quick Start establishes `client` once). Card's stated initiative 'opt all 13 in if they all compile' therefore does not hold, and the page's contributor note is rewritten to record this measurement rather than the retired TS2307 constraint." }, "data_service_mdx_disposition": { "action": "opted in, and carved onto the same scoped root", "fence_149": "the `services.data` example (imports ObjectStackClient as a type, exports a function) — 0 diagnostics, marked", "fence_33": "left unmarked — it is a listing of method signatures, not a statement; it does not PARSE (3x TS1005) and correctly produced the #12308 REFUSE verdict naming the new docs-sdk root when I marked it during the sweep", "prose": "the page's note is rewritten from 'the marker becomes worth adding the day this surface has a spec-side contract to import' to what is now true" }, "file_list": [ "packages/spec/scripts/check-skill-examples.ts", "content/docs/api/client-sdk.mdx", "content/docs/kernel/runtime-services/data-service.mdx" ], "verification": [ { "command": "pnpm --workspace-concurrency=2 --filter '@objectstack/client-react...' build", "verdict_line": "os-verify-lock: VERDICT command-exit 0 · held the lock 285s (4m45s) · waited 51s" }, { "command": "pnpm --filter @objectstack/spec check:skill-examples (AFTER, with the 4 markers)", "verdict_line": "✅ 260 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them", "counts": "skills + docs 227 · spec source TSDoc 10 · client SDK 23 (was 227 / 10 / 19 = 256 before the markers; the skills+docs count is UNCHANGED at 227, which is the carve-out being a partition rather than a subtraction)" }, { "command": "self-test (first half of check:skill-examples)", "verdict_line": "✅ self-test: … a page-scoped root scans exactly its list while the broad root over the same tree scans everything BUT it (with the un-partitioned control proving the page was reachable), the two roots produce distinct build-file names, a page claimed by two surfaces is caught and a correct partition is not, a `pages` entry naming a file that is not there is caught and an existing one is not — and the real SURFACES satisfy both invariants today." }, { "command": "REVERSE VERIFICATION — git restore --source=b8419bd5a -- packages/spec/scripts/check-skill-examples.ts, markers left on disk, re-run", "ablation_confirmed_on_disk": "grep 'SDK_DOCS_PAGES' = 0 occurrences (was 5); markers still 3 on client-sdk.mdx + 1 on data-service.mdx — both counts read from the files themselves, not from an editor exit code", "predicted_direction": "red with TS2307 on the SDK imports", "observed_verdict_line": "✗ [skills + docs (@objectstack/spec)] examples do not compile: content/docs/api/client-sdk.mdx:524:30 error TS2307: Cannot find module '@objectstack/client' or its corresponding type declarations.", "observed_detail": "13 diagnostics: 5x TS2307 + TS7026/TS2875/TS7006 (the React block meeting a surface with no react types). The 4 blocks land back on the skills+docs surface (231 blocks there, vs 227 with the fix). 524:30 is the createFilter import — same statement and same COLUMN as the card's 485:30 control, at its post-#12045 line.", "restored": "git checkout HEAD -- <the script>; git status --porcelain empty; SDK_DOCS_PAGES back to 5 occurrences", "note": "no build was needed for either leg — this gate is tsx-run source, not a dist consumer; the dist it READS (spec/client/client-react) was built once up front and unchanged across both legs" }, { "command": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (NO paths passed; script derived the change set itself)", "verdict_line": "change set derived from git — 3 path(s) vs merge base b8419bd5a of 'origin/main' and HEAD (committed 3, working tree 0, untracked 0) … 173 check famil(ies) discovered across 28 workflow file(s)", "result": "34 local families — MORE than the dispatch named. Added by re-derivation: check: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. All 34 were run." }, { "command": "all 34 derived gates, each exit code captured by redirect-then-$? (never after a pipe)", "verdict_line": "33 of 34 EXIT=0", "green": "check:cross-package-test-inputs, check:doc-anchors, check:doc-authoring, check:doc-formula-expressions, check:doc-security-posture, check:docs, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:empty-state, check:liveness, check:merge-driver, check:page-declaration-shape, check:published-files, check:published-readme-links, check:react-page-adapter-contract, check:role-word, check:runtime-services-index, check:skill-examples, check:slot-lookup, check:strictness-ledger, check:test-source-alias, check:type-source-resolution, check:variant-docs, check-ci-filter-parity, check-cross-package-test-inputs, check-doc-frontmatter, check-doc-route-spelling, check-docs-section-name, check-plugin-teardown-shape, check-section-landing-index, docs-audit/check-affected-docs, docs-audit/check-drift-comment" }, { "command": "node scripts/check-nul-bytes.mjs", "verdict_line": "check-nul-bytes: OK (scanned 6833 text file(s) -- 6833 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes)." } ], "deviations": [ { "what": "DECLARED NARROWING — node scripts/check-dev-prereqs.mjs exits 1 in this worktree", "gate_own_words": "✗ The workspace is not built — 1 unmet precondition, not a list of problems. 34 of 67 workspace packages declare an entry point under dist/ that is not on disk: @objectstack/account, @objectstack/setup, @objectstack/studio … and 31 more. Fix: pnpm build", "why_it_is_not_my_diff": "Only the @objectstack/client-react dependency closure was built here, not the full workspace. My diff is 3 files and touches zero package.json, zero dist/, zero build config (measured: `git diff --name-only BASE..HEAD | grep -cE 'package\\.json|dist/|tsup|turbo'` = 0), and none of the 34 named packages. CI runs this gate after its 'Build workspace packages' step. A full pnpm build to clear it would have held the contended shared lock for many minutes to re-prove a known environment fact." }, { "what": "CHANNEL CONFLICT worth PM's attention — the standing os-dev clause requires the duplicate search before filing a finding to go through the REST list endpoints, NOT MCP list_issues/search_issues (GraphQL pool). The dispatch's channel fact says raw REST/gh is unavailable on this seat.", "measured": "curl to api.github.com/repos/.../issues returned HTTP 403; `command -v gh` = absent. The channel fact holds, so the clause's prescribed route does not exist on this seat.", "what_i_did": "used MCP search_issues once (2 results, both closed, neither a duplicate) rather than skipping the de-dup step. Flagging rather than silently picking a side: the clause and the channel fact cannot both be satisfied here." }, { "what": "skip-changeset label applied via the REPLACE path, not the additive endpoint", "why": "the additive REST endpoint the clause prescribes is the same 403 channel. Read current labels first (['size/m']), wrote the union (['size/m','skip-changeset']) via MCP issue_write, then read back: ['size/m','skip-changeset'] — both present, nothing clobbered." }, { "what": "PR body sanitizer damage, caught by read-back and repaired", "detail": "an angle-bracket path placeholder after `git checkout HEAD --` was eaten on create. Re-read the body, replaced it with the literal path, re-read again: intact, and the session-URL footer survived both write paths." } ], "findings": [ "filed as #12342 (sub-issue of #11942): the two error-handling examples on client-sdk.mdx read error.code / error.fields off an untyped `catch` binding — under strict (useUnknownInCatchVariables) that is `unknown`, so 7 of the 114 diagnostics are TS18046 and a reader copying the block into their own strict project gets a compile error. Distinct from the marker/coverage question because it is true whether or not the block is ever marked. Hung under #11942 rather than filed loose, since that card owns this page's verification. De-dup search run first (see the channel-conflict deviation)." ], "downstream_note": "#11942's Unlock-action was 'when #12048 lands, re-run the marking exercise on this page; the reds that remain will then be the real divergences'. That re-run is DONE and recorded above: the remaining reds are 9 continuation fragments (the page's documented convention, not divergences) plus the TS18046 class now filed as #12342. Zero doc-vs-SDK divergences remain on this page today — the 3 newly-marked blocks are what will catch the next one." }Generated by Claude Code
os-litant commentedon Aug 25, 2026
Correction to the report comment above (
5415451124) — sanitizer damage, found by reading it back. Two angle-bracket tokens inside JSON string values were eaten; the JSON is still valid and every machine-parsed field is intact, so this is a repair-in-place note rather than a rewrite (rewriting a correct card is the worse failure).implementation_shape_C.surface_placementreads "buildFileName is____". It should read: buildFileName is label, then two underscores, then the page path with separators flattened, then two underscores, then the block ordinal — which is why a distinctlabelper root is load-bearing when two roots share one tree.verification[3].restoredreads "git checkout HEAD -- ;". The elided token is the pathpackages/spec/scripts/check-skill-examples.ts.Nothing else changed. Same mutation shape as the one already listed in
deviationsfor the PR body, arriving a second time on the surface it was already known on.Generated by Claude Code
os-litant commentedon Aug 25, 2026
PM review: ACCEPT — PR #12339, head
4a6b5a2(round R6, sessionsession_01NDGG54XF5gbTLdQzCtnaVV).The adopted route C landed in its strongest form, and the fork-back condition was not needed: a fourth
SourceRooton the EXISTING client-SDK surface — no new Surface entry, no new resolution environment, no new extraction code. Per-page scoping isSourceRoot.pages(explicit list, walk short-circuited) withexcludePagesas the broad root's complement, and both read the ONE sharedSDK_DOCS_PAGESconstant, so the partition is true by construction rather than maintained twice. The two failure modes that scoping introduces are each closed by a new asserted invariant (assertDisjointSourceFiles— a page compiled twice in two resolution environments;assertScopedPagesExist— a renamed page silently restoring the TS2307 gap), both self-tested in both directions and against the real SURFACES.The card's executable criterion is met and reverse-proven: the
createFilter()fence compiles with 0 diagnostics; ablating the script back to base with markers left on disk reds withTS2307: Cannot find module '@objectstack/client'at the same statement and same column as the card's control. Corpus: 256 → 260 marked examples; skills+docs unchanged at 227 (partition, not subtraction) — and the #12308 REFUSE semantics are exercised, not regressed (data-service.mdx's non-parsing signature listing correctly drew a REFUSE naming the newdocs-sdkroot during the sweep and stays unmarked).Honest census disposition accepted: 3 fences on client-sdk.mdx + 1 on data-service.mdx opted in; the 9 left unmarked have per-fence diagnostic readings (the page's documented continuation-fragment convention, zero TS2307 among them) and the in-page contributor notes now record the measurement instead of the retired constraint. The dev's #12342 (TS18046 catch-binding class, hung under #11942) is correctly separated from the coverage question.
Downstream: #11942's recorded unlock-action ("re-run the marking exercise; the remainder are the real divergences") is already executed and recorded here — remainder = 9 documented fragments + #12342, zero doc-vs-SDK divergences today. When PR #12339 lands I'll note that on #11942 for its owning lane to disposition.
Clause-②: does not fire — no
packages/spec/src/**path. Normal queue path: ready + auto-merge once every check on4a6b5a2is green.Generated by Claude Code