docs(client,runtime): the AI slot answers 501, not 404 — with its 401-first and /ai/agents arms - #17844
Conversation
…-first and /ai/agents arms Three source sites still described the pre-`capabilityUnavailable` behaviour, saying `/ai/*` 404s "AI service is not configured" when no AI service is mounted. The dispatcher has answered 501 since the shared exit landed: the `/ai/*` routes are registered unconditionally, so a request reaches a handler with nothing behind it, which is 501 Not Implemented and not 404. The replacement is deliberately narrower than "`/ai/*` answers 501", because that sentence is not true either. Verified in `domains/ai.ts` against the unserveable-slot branch and its pins: - an anonymous caller is refused 401 first (ANONYMOUS_DENY_STATUS); - `GET /ai/agents` answers 200 with an empty list as a console courtesy; - every other route answers 501 via `capabilityUnavailable(deps, 'ai')`. The client docblock additionally gains the `/ai/agents` empty-list courtesy, which it never mentioned, and all three sites now say the 501 body comes from the shared `serviceUnavailableMessage` and so cannot drift from what discovery reports for the slot. Comment text only — no runtime behaviour changes. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
…ock correction The docblock is emitted into all four of the client's built artifacts, so the published surface moves and `skip-changeset` would be wrong. Measured on the built tree with a lit control; the runtime-side siblings publish nothing and are deliberately not named. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 32 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0d4c77d2dd74403ef47c062b8c868ce8917c5bd3 && git checkout 0d4c77d2dd74403ef47c062b8c868ce8917c5bd3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6059b29c03e470dbaa513cc3f0992a8e610855be c3b4c470f835e71ca501b385a422bf631876254c && git checkout -B drift-repro 6059b29c03e470dbaa513cc3f0992a8e610855be && git merge --no-ff c3b4c470f835e71ca501b385a422bf631876254c
node scripts/docs-audit/affected-docs.mjs --json 6059b29c03e470dbaa513cc3f0992a8e610855be
|
Fixes #16211
Clause-②: no
Re-derived on the final diff, not inherited. Every changed line is comment or docblock text with one exception, which is a data literal and not a comment: the
* /ai/**row'snote:string inROUTE_LEDGER(packages/runtime/src/route-ledger.ts). It moves no response byte all the same —route-ledger.tsis not re-exported frompackages/runtime/src/index.ts,ROUTE_LEDGERis absent frompackages/runtime/distentirely, and no non-test source reads.noteas a served value; the ledger is a CI-audit artifact whose conformance test asserts note presence (!e.note), never its text. Apart from that one string the diff adds and removes only*///prose, and a mechanical scan for any other non-comment added or removed line returns empty. No export, authorable key, accept set, status code or response byte moves.File face — declared in full
packages/client/src/index.tsainamespace docblockpackages/runtime/src/route-ledger.ts* /ai/**row'snote:prosepackages/runtime/src/domains/ai.ts.changeset/16211-ai-slot-501-not-404.md@objectstack/client: patchpackages/spec/src/api/protocol.zod.ts⛔ The out-of-face site, reported rather than fixed
packages/spec/src/api/protocol.zod.ts:2787(at6b2ec3b97; locate by the sentence) carries the same stale claim:「凡触
packages/spec一律转domain:spec座位,不论谁需要它」 — so it is left exactly as found, and ⛔ no card was filed in that lane by this seat. Thedomain:cliPM seat files the companion at ACCEPT.1. The sweep — triage's «four» is correct; the dispatch order's «three» is an artifact of its matcher
The order asked for this reconciliation as the first deliverable, with a lit and a negative control per pass. Tree:
objectstack-ai/objectstackat6b2ec3b97.Pass A — exact phrase, whole tree.
git grep -n 'AI service is not configured'→ 3 live source sites (client/src/index.ts,runtime/src/route-ledger.ts,spec/src/api/protocol.zod.ts).Lit control
not configured→ 45 files. Negative controlzzz_absent_16211→ exit 1, 0 lines.Pass B — the card's own executable criterion, widened repo-wide.
git grep -n '404s'excluding archive → 188 lines, among them the site Pass A structurally cannot see:Pass C/D — census cross-check. Every source file mentioning both
404and/ai/(49 candidates) was read. Exactly two other files state an AI-related 404 —skills/objectstack-ai/SKILL.md:344andpackages/lint/src/validate-ai-agent-authoring.ts:13— and both are a different mechanism: ADR-0063 §2 stack-authored agents refused byloadAgent(). They are correct as written and are not a fifth site.⇒ The live-source site census is four. Triage was right. The dispatch order's «three» is right for its matcher and misses
domains/ai.tsbecause that site never spells the phrase — which is precisely the site the addendum called the most surprising one, sitting two lines above its own falsifier.The order attributes «3 source files plus 4
CHANGELOG.mdoccurrences» togit grep -n 'AI service is not configured' origin/main. Re-run verbatim, that command returns 3 lines and zero CHANGELOG hits — the archive text is line-wrapped, so the exact phrase is split and cannot match:The 4 occurrences are real (2 in
packages/client/CHANGELOG.md, 2 inpackages/spec/CHANGELOG.md) but are found by the wrapped prefix, not by the matcher they are credited to. It changes no conclusion; recorded because the order asked for its own claims to be falsified rather than satisfied. ⛔CHANGELOG.mdis archive and was not edited.2. The replacement is narrower than "
/ai/*answers 501" — all three arms verified in sourceVerified directly against the unserveable-slot branch in
packages/runtime/src/domains/ai.ts, in evaluation order, ⛔ not taken from the order or the card:ANONYMOUS_DENY_STATUS = 401(packages/core/src/security/anonymous-deny.ts:66)GET /ai/agents{ agents: [] }deps.success({ agents: [] })/ai/*capabilityUnavailable(deps, 'ai')→deps.error(serviceUnavailableMessage(slot), 501)All three are already pinned in
domains/ai-anonymous-deny-ordering.test.ts(Group A anonymous-deny; Group B501with the remedy sentence and the200agents envelope). Replacing the sites with the obvious sentence would have manufactured a second inaccurate statement, so each site now carries the exceptions.Two further details the stale text lost, now stated:
/ai/*is registered unconditionally, which is why the answer is 501 and not 404; and the 501 body is not a local string — it comes from the sharedserviceUnavailableMessage, the same sentence discovery reports for the slot, so the two cannot drift.⭐ The
GET /ai/agentsempty-list courtesy was mentioned nowhere in the client docblock, the one an SDK reader actually opens. It is added there, as the deliverable triage named.3. Changeset, not
skip-changeset— measured@objectstack/clientshipsfiles: ["dist","README.md","CHANGELOG.md"], and this TSDoc is emitted into all four built artifacts (dist/index.d.ts,dist/index.d.mts,dist/index.js,dist/index.mjs) — measured on the built tree, with the stale sentence absent from every built file afterwards and a neighbouring docblock sentence present as the lit control. So the published surface moves:patch.The two runtime-side siblings publish nothing and are deliberately not named in the changeset:
ROUTE_LEDGERis absent frompackages/runtime/distentirely, and three pre-existingdomains/ai.tscomments probed as controls appear in zero built files (while that dist retains 896 comment lines, so this is not blanket comment-stripping).4. Verification — at
c3b4c470fGate families derived mechanically, ⛔ not hand-listed:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack. Each exit code captured before any pipe; each verdict read from the gate's own printed line.--ran:58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN.PREREQUISITE NOT MET(check:skill-examples,check:dual-build-cjs-loads— both read built output that did not exist yet). ⛔ Not recorded as failures: a fullpnpm buildwas run (73/73 tasks) and both were re-run to a real verdict —258 prose examples type-check across 3 surface(s)and104 published require entry point(s) across 67 package(s) load.pnpm typecheck+pnpm testfor both affected packages, under the shared heavy-verify lock (VERDICT command-exit 0):@objectstack/runtime260 files / 3623 tests passed;@objectstack/client**43 files / 519 tests passed`.pnpm lint(this lane always adds it): exit 0, no findings.check:nul-bytesgreen, plus an independent control-character scan of all four changed files: 0 hits.Named, not measured
Verified by Claude Code in session
session_01TSf4DV7ziu4V5j73e46b7c.Generated by Claude Code