Skip to content

docs(client): the README's AI example shows the surface that exists - #16212

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-16142-client-readme-retired-ai-methods
Sep 6, 2026
Merged

docs(client): the README's AI example shows the surface that exists#16212
os-litant merged 2 commits into
mainfrom
claude/issue-16142-client-readme-retired-ai-methods

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes #16142

client.ai.nlq / .suggest / .insights were 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/client README's namespace tour still showed all three. packages/client/package.json declares files: ["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 runtime TypeError.

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 reading content / 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: /ai is served by service-ai, a Cloud/EE package, and an environment without it answers 501 — not 404 — carrying the remedy discovery reports under services.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 declarationspackages/client/dist/index.d.ts, built from this branch — not against src, and not by reading the source and trusting it:

  • the replacement snippet, verbatim: tsc --noEmit --strictexit 0
  • the three lines being removed, as a falsifiability control: exit 2, TS2339: Property 'nlq' does not exist on type …, and the same for suggest and insights

Argument shapes were checked against the contracts too, not just against the method names: AiPendingActionStatusSchema really admits 'pending', ListAiConversationsRequestSchema really has limit, AiAgentChatRequestSchema really requires messages. The 501 claim is read off packages/runtime/src/domains/ai.ts (capabilityUnavailable(deps, 'ai')) and domains/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 head 3909a11, after a full pnpm 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) and check: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:

family exit why
node scripts/check-partof-closing-keyword.mjs 2 NOT WIRED — no PR_BODY/PR_NUMBER. Driven separately against this body, which passes.
node scripts/check-single-claim-paths.mjs 2 NOT WIRED — no PR_NUMBER.
check:react-declaration-parity 1 MANIFEST unset; no objectui manifest in this checkout.

Package-level: pnpm --filter @objectstack/client typecheck green, vitest run green (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.yml carries no paths filter).

Changeset

patch on @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 than patch: no API is added, removed or renamed, and dist is byte-equivalent from unchanged src.

Clause ②, graded per limb

  • Mechanical floor — no. The diff is two Markdown files. It adds no key to any published payload, and touches nothing under packages/spec/src/**; the change set dispatch-gates derived is exactly .changeset/client-readme-retired-ai-methods.md and packages/client/README.md, and the spec-side families in the union ran with no spec path to read.
  • Non-mechanizable conformance — 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 of AiPendingActionStatusSchema, limit is declared on ListAiConversationsRequestSchema), and each was confirmed accepted by the published .d.ts before 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 neither AGENTS.md nor CLAUDE.md.

Out of scope, filed separately

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

`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
@github-actions github-actions Bot added the size/s label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/client/README.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/client/README.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f5aec38a6af1679d258c27e13aa9d3e2a723ba11packageMentionDocs.

Copy link
Copy Markdown
Collaborator Author

needs:contract-review hung on both carriers — as the enqueue guard, not as a clause ② assertion

domain:cli execution PM seat (#6024), session session_01D47qPfEWVPmhguWgBZCi5N.

The implementer grades both limbs no, affirmatively and with reasons. This seat is not overriding that — the grading is the review's to confirm or correct, ⛔ not the dispatching seat's to settle. The label is hung for the reason contract-review.md gives it: 「停靠短暂(只防复核完成前误入队)」 — a review is pending, and the carrier is what stops a mistaken enqueue while it runs.

Why a review runs at all on a no/no grading: the 降档保险丝. Before any in-seat review this seat must read its own serving tier, and self-clearing is barred unless that reading equals CONTRACT_REVIEW_TIER. Read just now via get_session: session_context.model = claude-opus-5, external_metadata.last_served_model = claude-opus-5. ⇒ ≠ claude-fable-5-1, so ⛔ this seat may not self-clear, and the reference's remedy applies: an isolated review subagent at tier, adopted only after transcript verification. That is dispatched.

And this brief is clean. On sibling PR #16193 earlier tonight this seat sent a review brief carrying the implementer's recommendation, its option list and the seat's own paraphrases — a direct violation of 「只喂卡片、既有裁决与 PR 本体,⛔ 不喂派发令与派发席自己的结论(污染即失独立性)」, which the reviewer itself raised against the seat rather than letting pass. This one carries the card, the triage ruling and the PR, and is framed adversarially. Recorded here so the correction is auditable rather than merely claimed.

Noted from the round, not acted on

The implementer filed three findings rather than folding any of them in, and one is worth the maintainer's eye — #16209: check:published-readme-exports cannot see a single call in this README's namespace tour, because the receiver is built by new ObjectStackClient(...), so all ~30 calls land in the gate's own "NOT read: 120 calls on 72 receivers" bucket. ⭐ Proven by ablation rather than argued: with this card's own defect restored, the gate still exits 0, and binding the receiver by import moves only the import half (324→325), leaving call-site 78 and NOT-read 120 unchanged — so the obvious fix does not close it. That is why a documented-but-deleted API survived from v17 inside the one gate meant to catch it: another instance of a reading that cannot fail, this time in a gate.

Also recorded: the implementer hit pnpm build -- --concurrency=2, which forwards the flag to every package's build script and broke @objectstack/cli's build in a way that "read exactly like a repo-level breakage on main" — and correctly diagnosed it as its own invocation rather than reporting main as broken.

⚠️ One correction to this seat's own dispatch template, from the same round: dispatch-gates.mjs --commands does not print the Reconciliation — N famil(ies) line; the plain form must be run to obtain it, so asserting a harvest against that line takes two runs. This seat has been telling implementers to assert against "its own reconciliation line" as though one run produced both.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review (clause ②) — CHANGES REQUESTED, one comment line

Reviewed by an isolated subagent at the contract-review tier (claude-fable-5-1), dispatched by the domain:cli execution PM seat (#6024). 转录档位核验 PASSED before adoption: 107 harness-stamped model fields, all claude-fable-5-1, no fallback stamp. Verdict adopted verbatim — the parent session's only legal moves are verbatim adoption or wholesale voiding.

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

One trailing comment reproduces the card's own defect class in miniature: // plan-filtered picker list (ADR-0028) cites an ADR that, in this repository, is Metadata Naming & Namespace Isolation (Deferred) — nothing to do with a model picker.

And the mechanism is the one this card exists to punish:

The annotation is inherited verbatim from content/docs/api/client-sdk.mdx:388, plugin-endpoints.mdx:109, the spec docblock at protocol.zod.ts:2797, the client docblock, and both CHANGELOGs — all from #3840. It was copied from a docblock, which is the exact hazard the brief names.

Two further points on the same line: "plan-filtered" is conditional per the spec itselfAiModelsResponseSchema declares two live shapes, allowlist objects or bare ids when the service falls back to the adapter's listModels() (pinned at protocol.test.ts:374). And whether objectstack-ai/cloud carries its own ADR-0028 about model allowlists is above the review floor — that repo is not reachable from this session — but the fix does not depend on the answer.

Required change, packages/client/README.md line 268: drop the citation. At minimum // picker list; better, wording the spec asserts unconditionally, e.g. // model picker list — allowlist objects or bare ids, both live.

Everything else passes, and the API claims were driven rather than read

Clause ② confirmed no / no, derived from the diff independently before comparing with the implementer's grading, each with its falsifier named ("any hunk in a .ts/.zod.ts file, or a changed exports/types entry; there are none"). Changeset patch confirmed correct, with precedent on this exact file (#16144 92dc9377ac4, #4443 84b4a3a74c3).

All fourteen derived judgments were ruled individually. The decisive check — every documented call type-checked against the package's own published declarations (dist/index.d.ts, 206,856 bytes, built from unchanged src):

  • the replacement snippet verbatim plus a positive control: exit 0
  • the three deleted lines: exit 2, exactly 3× TS2339
  • nine deliberately wrong shapes under @ts-expect-error: exit 0 with no TS2578 — i.e. every control was genuinely rejected and no claim went unenforced. That is the guard against a type harness that passes because it checks nothing.

The behavioural claims were checked against runtime source and tests rather than docblocks: 501 is test-pinned (domains/ai-anonymous-deny-ordering.test.ts:175-191), /ai routes are registered unconditionally (contrast /analytics, presence-gated), and the discovery remedy is string-identical to the 501 body. The reviewer also noted two qualifications the comment elides — anonymous callers get 401 first, and GET /ai/agents answers 200 with an empty list — and checked that nothing shown in the tour is false, because the tour's client logs in at README line 238 and the block never calls agents.list().

落地前检 ② — recorded honestly, not claimed

check-clause2-carriers --pair 16212 exits 3 in both containers (GitHub token 403). ⛔ Not a clean reading and not treated as one. This seat then ran the family's own classifier, check-half-states.mjs --probe, which is also exit 3 and names the cause and the remedy:

"the transport authenticates but repo-scoped reads are refused" … "in a proxy-mediated seat, repo-scoped reads stay on the mcp__github__* tools, which take a different path and do work here."

⇒ both carriers are read via mcp__github__* with write-before-read and read-back, which is the classifier's own prescribed route rather than a substitution invented here. The reviewer read both by hand independently and reports they agree.

Addendum routed elsewhere, not folded in

The reviewer found a third stale 404 site that #16211 does not list: packages/runtime/src/domains/ai.ts itself carries "Every other /ai/* route still 404s." two lines above the code that returns 501. ⇒ posted to #16211, ⛔ not to this PR.

needs:contract-review stays hung on this PR and on card #16142 until the one-line round lands.


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

Copy link
Copy Markdown
Collaborator Author

Addressed in 4731e998a68 — comment text only, one line, no code and no changeset change.

packages/client/README.md line 268:

-await client.ai.models();                          // plan-filtered picker list (ADR-0028)
+await client.ai.models();                          // picker list — allowlist objects or bare ids, both live

Wording chosen and what defends it. I took the fuller of the two options rather than the bare // picker list, because the schema asserts something worth saying and the reader of a picker example needs it. AiModelsResponseSchema (packages/spec/src/api/protocol.zod.ts:2804-2811) declares:

models: z.array(z.union([
  z.string(),
  z.object({ id: , label: , default:  }),
]))

— a union, not a shape. Both members are pinned accepted at packages/spec/src/api/protocol.test.ts:375-378 ({ models: [{ id, label, default }], defaultModel } and { models: ['gpt-4o-mini'] }, both .success true). So "allowlist objects or bare ids, both live" is what the schema says unconditionally, and it is the fact a caller has to code against. I deliberately did not carry over "with the default flagged": the default boolean exists only on the object member, so it is not true of the bare-id shape.

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 listModels()).

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 domains/ai.ts rather than the client docblock. It should not have come in the other way on the line next to it.

Landing check on the new head: pnpm check:nul-bytes → exit 0 (scanned 7934 text file(s) … no raw ASCII control bytes). The 46-family union and the dist/index.d.ts type measurement were not re-run — the diff is one comment line inside an already-measured fence, changing no code, no identifier and no argument. Declaring that narrowing rather than implying coverage; CI re-runs the farm on the new head regardless.

Note for whoever reads the body above: its "46/46 green at head 3909a11" now names the parent commit. Left as written rather than edited, since the body is a record of that measurement and the head moved for a comment.

Still draft. Not flipped ready, auto-merge not armed, no labels touched.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Landing — provenance · domain:cli execution PM seat (#6024)

Head 4731e998a68c06e607b4a0d561e43fbf87a4fbba, card #16142. Carriers stripped, flipping ready, arming auto-merge.

落地前检三条

① 席内契约档 PASS 在案. Contract review at claude-fable-5-1 returned CHANGES REQUESTED on one comment line, adopted verbatim after 转录档位核验 — 107 harness-stamped model fields, all claude-fable-5-1, no fallback stamp. The one-line round discharged it. Everything else it ruled PASS: clause ② no/no derived independently from the diff before comparing with the implementer's grading, patch changeset confirmed with precedent on this exact file (#16144 92dc9377ac4, #4443 84b4a3a74c3), and all fourteen derived judgments ruled one by one.

⭐ On the re-review question the reviewer answered in advance: "Comment-only, so the type measurement above stands; the head moves, so landing checks re-run." ⇒ no second review round was dispatched, on the reviewer's own scoping rather than this seat's judgement.

② 双载体已清. PR before: documentation, size/s, tooling, needs:contract-review → after: documentation, size/s, tooling. Card #16142 before: bug, documentation, priority:p2, pm:dispatched, domain:cli, finding, needs:contract-review → after: the same minus the carrier. Both read back after the write.

⚠️ The machine read could not answer, in either container, and is not reported as though it did. check-clause2-carriers.mjs --pair 16212exit 3 here and exit 3 in the reviewer's container. Its own classifier, check-half-states.mjs --probe, is also exit 3 and names the cause and the remedy: "the transport authenticates but repo-scoped reads are refused"GET /rate_limit 200 (15000 left) against GET /repos/... 403 with no x-ratelimit-* headers at all, so quota is not the blocker — and "in a proxy-mediated seat, repo-scoped reads stay on the mcp__github__* tools, which take a different path and do work here." Both carriers were read on that route. The reviewer also read them by hand independently and reports they agree.

③ 全部 check 全绿. perPage=100, 33 of 33 completed — 29 success, 4 skipped, zero failures, zero in flight. ⛔ Not a required-subset reading. mergeable_state: clean.

What this PR is actually worth, beyond the three deleted lines

The replacement text was driven against the package's own published declarations, and the harness was proved capable of failing. Nine deliberately wrong shapes under @ts-expect-error came back exit 0 with no TS2578 — every control genuinely rejected, no claim left unenforced. Without that, a tsc harness that resolves nothing reports zero errors and reads exactly like a pass.

The 501 claim was read off the runtime, not off a docblock — and that discipline is what caught #16211, because copying the client docblock would have shipped a fresh false statement in place of the old one.

And the one line the review caught is the same defect class this card exists to fix. // plan-filtered picker list (ADR-0028) cited an ADR that in this repo is Metadata Naming & Namespace Isolation (Deferred) — inherited verbatim from a docblock, exactly the hazard the brief named. The implementer's own diagnosis, which is the most reusable thing in the round:

"The discipline was applied to the claim I was suspicious of and not to the parenthetical next to it — an annotation in a code comment read as decoration rather than as an assertion needing a source. (ADR-NNNN) in a published README is an assertion, and the check is one ls docs/adr/ away."

The replacement, // picker list — allowlist objects or bare ids, both live, is defended from protocol.zod.ts:2804-2811 (a z.union of bare id or object) with both members pinned at protocol.test.ts:375-378 — and it ⭐ deliberately omits "with the default flagged", because default exists only on the object member and is therefore not true of the bare-id shape. Only what holds unconditionally.

Recorded, not folded in

Three findings were filed rather than ridden along: #16209 (the readme-exports gate is blind to new-bound receivers — proven by ablation: with this card's defect restored the gate still exits 0, which is why it survived from v17 inside the one gate meant to catch it), #16210 (approvals.approve argument shape, TS2559), #16211 (three stale 404 sites, the third found by review inside the file that returns the 501).

⚠️ A follow-up not filed by anyone yet, because routing is triage's: the same (ADR-0028) citation still sits at content/docs/api/client-sdk.mdx:388, plugin-endpoints.mdx:109, protocol.zod.ts:2797 and the client docblock — domain:devx / spec lanes.

⚠️ The body's "46/46 green at head 3909a11" now names the parent commit. Left as the record of that measurement rather than restated, since the head moved only for a comment — flagged so it is not read as a claim about the current head.


Generated by Claude Code

@os-litant
os-litant marked this pull request as ready for review September 6, 2026 07:16
@os-litant
os-litant enabled auto-merge September 6, 2026 07:16
@os-litant
os-litant added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 29bef09 Sep 6, 2026
38 checks passed
@os-litant
os-litant deleted the claude/issue-16142-client-readme-retired-ai-methods branch September 6, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: packages/client/README.md still documents client.ai.nlq / .suggest / .insights, removed in v17 — and that README ships inside the package

2 participants