docs(client): the README's AI example shows the surface that exists - #16212
Conversation
`client.ai.nlq` / `.suggest` / `.insights` were removed in v17 (#3718), and no server in any repo ever mounted `/api/v1/ai/{nlq,suggest,insights}` — every call 404ed for the whole life of the namespace. The `@objectstack/client` README's namespace tour still showed all three, and `files` ships `README.md` inside the tarball, so that example is the package's npm front page: a TypeScript reader copying it gets TS2339 and a JavaScript reader a runtime `TypeError`. Replaced with the live `ai` surface — `chat` (reading `answer.content` / `answer.usage`), `complete`, `models`, `conversations.list`, `agents.chat`, `pendingActions.list` — each call type-checked against this package's own published `dist/index.d.ts`, with the removed three kept as a control that still fails with TS2339 there. The comment also names the condition a reader would otherwise hit unexplained: `/ai` is served by `service-ai` (Cloud/EE), and an environment without it answers 501, not 404, carrying the remedy discovery reports under `services.ai`. The docs site's Client SDK page already carried this correction; it is untouched here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
📓 Docs Drift Check
What this run could not see
Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
Contract review (clause ②) — CHANGES REQUESTED, one comment lineReviewed by an isolated subagent at the contract-review tier ( ⭐ The brief was written clean this time: card + triage ruling + PR body only, framed adversarially, with no dispatch order and none of this seat's conclusions — correcting the independence violation this seat committed on #16193. The finding — this PR reproduces its own card's defect class, in miniature
And the mechanism is the one this card exists to punish:
Two further points on the same line: "plan-filtered" is conditional per the spec itself — Required change, Everything else passes, and the API claims were driven rather than readClause ② confirmed All fourteen derived judgments were ruled individually. The decisive check — every documented call type-checked against the package's own published declarations (
The behavioural claims were checked against runtime source and tests rather than docblocks: 501 is test-pinned ( 落地前检 ② — recorded honestly, not claimed
⇒ both carriers are read via Addendum routed elsewhere, not folded inThe reviewer found a third stale 404 site that #16211 does not list:
Generated by Claude Code |
ADR-0028 in this repository is Metadata Naming & Namespace Isolation
(Deferred) — it says nothing about a model allowlist or plan tiers, and no
ADR in docs/adr/ covers one. The citation was inherited verbatim from the
docblock chain rather than checked, which is the same failure this card
exists to correct.
"plan-filtered" went with it: `AiModelsResponseSchema` declares `models` as
a union of bare id strings and `{ id, label, default }` objects, and states
that both shapes are live — objects when the service exposes the allowlist,
bare ids when it falls back to the adapter's `listModels()`. Both are pinned
accepted in `protocol.test.ts`. The comment now says only what the schema
asserts unconditionally.
Comment text only; no code, no other line, no changeset change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
|
Addressed in
-await client.ai.models(); // plan-filtered picker list (ADR-0028)
+await client.ai.models(); // picker list — allowlist objects or bare ids, both liveWording chosen and what defends it. I took the fuller of the two options rather than the bare models: z.array(z.union([
z.string(),
z.object({ id: …, label: …, default: … }),
]))— a union, not a shape. Both members are pinned accepted at Both halves of the finding are gone: the citation, and "plan-filtered" — which the schema's own docblock makes conditional (objects when the service exposes the allowlist, bare ids when it falls back to the adapter's The mechanism is noted and taken: that annotation was inherited from the docblock chain instead of checked, which is precisely the failure mode this card exists to correct, and the same one I avoided on the 501 claim by reading Landing check on the new head: Note for whoever reads the body above: its "46/46 green at head Still draft. Not flipped ready, auto-merge not armed, no labels touched. Generated by Claude Code |
Landing — provenance ·
|
Fixes #16142
client.ai.nlq/.suggest/.insightswere deleted in v17 (#3718), and no server in any repo ever mounted/api/v1/ai/{nlq,suggest,insights}— every call 404ed for the whole life of the namespace. The@objectstack/clientREADME's namespace tour still showed all three.packages/client/package.jsondeclaresfiles: ["dist", "README.md", "CHANGELOG.md"], so that text is the npm front page of the package: a TypeScript reader copying it gets TS2339, a JavaScript reader a runtimeTypeError.What changed
packages/client/README.md, the "AI Services" block of the namespace tour — lines 262-265 on the tree this branch was cut from (origin/main@a7cce55038c). The card said 262-264 and triage corrected it to 263-265; both were counting only the three call lines, and the comment header on 262 is part of the block being replaced. #16144 landed in this same fence after triage read it, but its only hunk starts at old line 269 — below this block — so the numbers did not move.Replaced with the surface the client really exposes, rather than deleting the section:
ai.chat(assigning the result and readingcontent/usage, matching the payload-read style #16144 just established two blocks down),ai.complete,ai.models,ai.conversations.list,ai.agents.chat,ai.pendingActions.list.The block also now names the condition a reader would otherwise hit unexplained:
/aiis served byservice-ai, a Cloud/EE package, and an environment without it answers 501 — not 404 — carrying the remedy discovery reports underservices.ai. Documenting a method that exists but always fails on a default install would have reproduced this card's defect with fresher names.How the replacement was driven
Every line was type-checked against the package's own published declarations —
packages/client/dist/index.d.ts, built from this branch — not againstsrc, and not by reading the source and trusting it:tsc --noEmit --strict→ exit 0TS2339: Property 'nlq' does not exist on type …, and the same forsuggestandinsightsArgument shapes were checked against the contracts too, not just against the method names:
AiPendingActionStatusSchemareally admits'pending',ListAiConversationsRequestSchemareally haslimit,AiAgentChatRequestSchemareally requiresmessages. The 501 claim is read offpackages/runtime/src/domains/ai.ts(capabilityUnavailable(deps, 'ai')) anddomains/unavailable.ts(deps.error(serviceUnavailableMessage(slot), 501)) — not from the client docblock, which still says 404 and is stale (filed as #16211).Verification
Gate union derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, asserted against that tool's own line:Reconciliation — 46 famil(ies). All 46 harvested and run individually, exit code captured before any pipe. 46/46 green at head3909a11, after a fullpnpm build(72/72 tasks).Two of the 46 first came back NOT MEASURED on a partially built tree and were re-measured green after the build, rather than being folded into the count:
check:published-readme-exports(exit 3, PREREQUISITE NOT MET) andcheck:dual-build-cjs-loads(exit 3).check:skill-examples(exit 1, "packages/client-react/dist holds no .d.ts") likewise re-measured green.Artifact rosters — the 39 families that sit outside that total — were run separately: 36 green, 3 NOT MEASURED, none of them a verdict on this diff:
node scripts/check-partof-closing-keyword.mjsPR_BODY/PR_NUMBER. Driven separately against this body, which passes.node scripts/check-single-claim-paths.mjsPR_NUMBER.check:react-declaration-parityMANIFESTunset; no objectui manifest in this checkout.Package-level:
pnpm --filter @objectstack/client typecheckgreen,vitest rungreen (33 files, 437 tests). The 10 families that declare a population too wide to place are outside all of the above and run unconditionally in CI (lint.ymlcarries no paths filter).Changeset
patchon@objectstack/client. Not "docs-only, so nothing": the README is a published file of this package, so correcting it changes what the tarball ships — the same reading #16144 took on this exact file. Not higher thanpatch: no API is added, removed or renamed, anddistis byte-equivalent from unchangedsrc.Clause ②, graded per limb
no. The diff is two Markdown files. It adds no key to any published payload, and touches nothing underpackages/spec/src/**; the change setdispatch-gatesderived is exactly.changeset/client-readme-retired-ai-methods.mdandpackages/client/README.md, and the spec-side families in the union ran with no spec path to read.no. Nothing here re-selects an input class between two published verdicts on a shipped face. The runtime and type surface are unchanged; the example arguments were chosen to sit inside verdicts the contracts already publish unambiguously ('pending'is a member ofAiPendingActionStatusSchema,limitis declared onListAiConversationsRequestSchema), and each was confirmed accepted by the published.d.tsbefore being written down. No case arose where a face had two live readings and this PR picked one.No governed surface is touched: nothing under
docs/adr/**,.claude/**,skills/**, and neitherAGENTS.mdnorCLAUDE.md.Out of scope, filed separately
check:published-readme-exportscannot see any call inpackages/client/README.md's namespace tour — the receiver is built bynew, so ~30 documented calls on the npm front page are ungraded #16209 —check:published-readme-exportscannot see a single call in this fence (receiver built bynew), which is why this defect survived from v17 inside the one gate meant to catch it. Measured by ablation: with the retired lines restored the gate still exits 0, and binding the receiver by import moves the import half 324→325 while leaving the call-site half at 78 / NOT-read at 120.packages/client/README.mdcallsapprovals.approve(requestId, 'LGTM')— the second parameter is a decision object, so the npm front page fails with TS2559 #16210 —approvals.approve(requestId, 'LGTM')in this same fence: the second parameter is a decision object, TS2559 against the published.d.ts. A different defect class, so untouched here./ai/*404s "AI service is not configured" — the dispatcher has answered 501 since the sharedcapabilityUnavailableexit landed #16211 —packages/client/src/index.tsandpackages/runtime/src/route-ledger.tsboth still say/ai/*404s; the dispatcher has answered 501 since the sharedcapabilityUnavailableexit landed.Not addressed here, and left open on their own terms: #14546 is one payload read per example, a separate defect class; #16141 is a different lane (
domain:devx,content/docs/). Neither is touched by this PR.Draft on purpose — landing is the PM seat's step after contract review.
🤖 Generated with Claude Code
https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Generated by Claude Code