Skip to content

feat(client,spec)!: express the AI surface that exists, retire the declarations that never did (#3718) - #3840

Merged
os-zhuang merged 3 commits into
mainfrom
claude/ai-namespace-unreachable-0qbyx2
Jul 28, 2026
Merged

feat(client,spec)!: express the AI surface that exists, retire the declarations that never did (#3718)#3840
os-zhuang merged 3 commits into
mainfrom
claude/ai-namespace-unreachable-0qbyx2

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes the second half of #3718. Recommendation there was 2 + 3; v17 (#3736) did option 2's client half, this does the rest of 2 and all of 3.

The finding, restated

before v17after v17after this PR
client.ai methods3 (nlq, suggest, insights)010
…of which any repo mounts010
mounted AI routes with SDK expression0 / 120 / 1210 / 12

The two sets were disjoint. v17 deleted the dead methods; this expresses the surface service-ai really mounts.

What the SDK gains

MethodRoute
ai.chat(request)POST /api/v1/ai/chat — sends stream: false, so the JSON mode is what you get
ai.chatStream(request)POST /api/v1/ai/chatAsyncIterable of UI Message Stream frames
ai.complete(request)POST /api/v1/ai/complete
ai.models()GET /api/v1/ai/models (ADR-0028 plan-filtered picker list)
ai.conversations.create/list/get/update/delete/addMessagethe six /api/v1/ai/conversations routes

Two details worth review:

  • chat forces stream: false. The endpoint streams unless told otherwise, so a "JSON" method that stayed silent would hand res.json() an SSE body.
  • chatStream returns Promise<AsyncIterable<…>>, not an async generator. The request is issued — and an HTTP error thrown — when you call it, not when you first iterate. Its parser reads the body line by line, not frame by frame: the encoder also emits single-\ng: reasoning lines that a \n\n split would glue onto the next event.

The two routes left unexpressed are deliberate and noted in the ledger: GET /status and GET /effective-model are operator diagnostics (server-only), and POST /chat/stream is the generic-SSE twin of /chat without the tool loop or persistence — a second near-identical streaming method would buy a consumer nothing but a wrong default.

Breaking: the spec's dead AI declarations are retired

None had an implementation anywhere, and none had a runtime consumer.

  • Ai{Nlq,Suggest,Insights}{Request,Response}[Schema] → replaced by the wire shapes of the real routes (AiChat*, AiStreamChunk, AiCompleteRequest, AiModelsResponse, AiConversation, AiMessage, {Create,List,Update}AiConversation*). The six retired JSON Schemas leave json-schema.manifest.json deliberately (gen:schema silently drops PageTabsProps since #2967 — references regen would delete real docs #2978).
  • DEFAULT_AI_ROUTES → deleted; getDefaultRouteRegistrations() returns 8 groups. Re-declaring the real routes there would recreate the same illusion — they are mounted from another repo, and this table has no runtime consumer at all.
  • AiProtocol (aiNlq? / aiSuggest? / aiInsights?) → deleted. Nothing implemented it, nothing dispatched through it. The real server contract is IAIService + IAIConversationService in @objectstack/spec/contracts.

Also corrected: a manifest.zod.ts JSDoc example that used ['aiNlq', 'aiChat'] as its illustration of protocol method names.

The guard — why a prefix exemption is not a wave-through

/api/v1/ai/ becomes a bounded prefix exemption in the capstone (#3642), alongside the control plane, and bounded from both ends: only ai.* may use it, and the namespace must still be reaching it. The reachability check lives where the routes are — cloud's packages/service-ai/src/ai-route-ledger.conformance.test.ts reads the table buildAIRoutes() returns and drives this SDK against it, so an ai.* URL that stops resolving fails a test in the repo that mounts it (objectstack-ai/cloud#…, branch claude/ai-namespace-unreachable-0qbyx2).

The wildcard-only bound stays 0: these URLs are checked beforematches(), so they never touch the * /ai/** row that certified three dead methods for years.

Tests

The four replaced client tests mocked fetch and asserted the URL the client built, never that anything answered it — they passed for years against endpoints that did not exist. The new ones assert only what this repo can honestly know (verb, path, and the body decisions the SDK makes for the caller: stream: false, the 204 on delete, SSE frame parsing) and leave "does it resolve" to the ledger next to the routes.

Verified locally: spec 6745/6745, client 178/178, runtime 665/665, full pnpm build green. The cloud half was run against this branch's build — 18/18 in the ledger conformance file, 581/581 in service-ai — and mutation-checked: repointing ai.models at /modelz fails exactly two tests there, naming the method and the URL it built.

Cross-repo ordering

Land this first, then bump .objectstack-sha in the cloud PR. Cloud's pin drives staging deploys, so it must move to a main commit, never a feature branch.

Not in this PR

The console's developer API-discovery panel (objectui apps/console/src/pages/developer/hooks/useApiDiscovery.ts) still lists /nlq, /suggest, /insights with "try it" bodies — three endpoints that always 404. objectui is outside this session's repo scope; it needs the same trim plus the real routes, and is the last piece of #3718.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN


Generated by Claude Code

…clarations that never did (#3718)
`client.ai` and the real AI service were disjoint sets. The namespace held
`nlq` / `suggest` / `insights`, whose URLs no repo has ever mounted (deleted in
v17), while `service-ai` mounted 12 routes the SDK could not reach at all.
v17 closed the first half. This closes the second.
The SDK now expresses every AI route meant to be tenant surface:
ai.chat / ai.chatStream POST /api/v1/ai/chat (JSON | UI Message Stream)
ai.complete POST /api/v1/ai/complete
ai.models GET /api/v1/ai/models
ai.conversations.{create,list,get,update,delete,addMessage}
`chat` forces `stream: false` — the endpoint streams by default, so the JSON
method has to say so or `res.json()` throws on the first frame. `chatStream`
returns a promise for an async iterable rather than being an async generator,
so the request is issued (and an HTTP error thrown) on call, not on first
iteration. Its SSE parser reads line-by-line, not frame-by-frame: the encoder
also emits single-`\n` `g:` reasoning lines that a `\n\n` split would glue onto
the next event.
The spec's dead AI declarations go with them — none had an implementation
anywhere, and none had a runtime consumer:
- Ai{Nlq,Suggest,Insights}{Request,Response}[Schema] → the wire shapes of the
routes that exist (AiChat*, AiStreamChunk, AiCompleteRequest,
AiModelsResponse, AiConversation, AiMessage, {Create,List,Update}Ai*).
The six retired JSON Schemas leave json-schema.manifest.json deliberately
(#2978).
- DEFAULT_AI_ROUTES → deleted; getDefaultRouteRegistrations() returns 8
groups. Re-declaring the real routes there would recreate the same
illusion: they are mounted from another repo, and this table has no
runtime consumer.
- AiProtocol (aiNlq? / aiSuggest? / aiInsights?) → deleted. Nothing
implemented it, nothing dispatched through it. The real server contract is
IAIService + IAIConversationService in @objectstack/spec/contracts.
Guard: /api/v1/ai/ becomes a bounded prefix exemption in the capstone (#3642)
alongside the control plane — only `ai.*` may use it, and the namespace must
still be reaching it. The reachability check lives where the routes are, in
cloud's ai-route-ledger.conformance.test.ts, which reads buildAIRoutes() and
drives this SDK against it. The wildcard-only bound stays 0: these URLs never
touch the `* /ai/**` row that certified three dead methods for years.
The four replaced client tests mocked fetch and asserted the URL the client
BUILT, never that anything answered it. The new ones assert only what this repo
can honestly know — verb, path, and the body decisions the SDK makes for the
caller — and leave "does it resolve" to the ledger next to the routes.
Refs #3718, #3708, #3642, #3563
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
@vercel

vercelBot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredJul 28, 2026 9:31am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/xl labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/client, @objectstack/runtime, @objectstack/spec.

114 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via packages/client, @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/client, packages/runtime, @objectstack/spec)
  • content/docs/api/data-flow.mdx(via @objectstack/client)
  • content/docs/api/environment-routing.mdx(via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/client, @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/client)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/client, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/client, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via packages/runtime, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/client, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx(via @objectstack/client)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/client, @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/client, @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

claude added 2 commits July 28, 2026 09:23
…e wire field name (#3718)
`check:role-word` (ADR-0090 D3) flagged 2 new occurrences in
content/docs/api/client-sdk.mdx: the `{ role: 'user', content: … }` messages in
the new `ai.chat` / `ai.conversations.addMessage` examples.
Baselined rather than reworded, for the reason the check itself names as
legitimate: this is a genuine external boundary. `role` is the field name in
the Vercel AI SDK `ModelMessage` shape the AI routes accept — the same class as
the better-auth and ARIA occurrences already in the baseline. Renaming it in the
example would document a payload the server rejects.
Nothing else moved: the baseline gains exactly one line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
…tirement (#3718)
`check:api-surface` is the ratchet that makes a removed export impossible to
ship by accident: 14 breaking (removed/narrowed), 22 added. Both halves are
this change and nothing else —
removed Ai{Nlq,Suggest,Insights}{Request,Response}[Schema] (12),
DEFAULT_AI_ROUTES, AiProtocol
added AiChat{Request,Response}[Schema], AiStreamChunk[Schema],
AiCompleteRequest[Schema], AiModelsResponse[Schema],
AiConversation[Schema], AiMessage[Schema],
{Create,List,Update}AiConversation*[Schema] (22)
The removals are already declared as a `major` on @objectstack/spec in the
changeset, which is what the gate asks for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude