test(spec,client,runtime): both producers of /ai/agents are enveloped now — relocate the declaration and pin it (#4053) - #4238
Merged
Conversation
… now — relocate the declaration and pin it (#4053) #4053 asked for three steps plus one relocation. The three landed: the framework's degraded fallback (#4124), cloud's `service-ai` (cloud#929), and the SDK read — which needed no change, and that is the finding rather than an omission. What was left is the relocation the issue named, and a set of records that all still say the conversion is half done. ## The declaration follows the wire `AiAgentsResponseSchema` was declared as the whole `GET /api/v1/ai/agents` body. Since the conversion it is what travels under `data`, so it now says so — the same move `SettingsNamespacePayload` made in #3843, and the reason this was a RELOCATION rather than #3983's flatten is recorded on the declaration instead of only at the two producers that implement it. ## One pin, in the package both producers share `packages/spec/src/api/ai-agents-envelope.test.ts` asserts BOTH halves of the contract — `envelopeViolations` clean AND `data` parsing as the declared payload — with the drifts pinned as negatives: the flattened conversion, the pre-#4053 bare body, an `agents` key mirrored beside `data`, and a row with no `capabilities`. Note which check catches the flatten. The envelope is *fine*; it is the payload that stops matching its declaration. A suite leading with `envelopeViolations` alone passes that body, which is exactly how it would ship. This lives in spec because nothing else can hold it. Each producer pins its own body in its own repo and neither can pin the other's; `scripts/check-route-envelope.mjs` reaches neither — dispatcher domains return `{ status, body }` for a central sender (#4049), and cloud is outside the framework guard entirely. What both producers do share is this package. ## Records that had gone stale Four comments still described cloud as unenveloped and the migration as in flight. On this route that is not cosmetic: the whole reason #4053 exists is that a wrong belief about the shape produces no error, no 403 and no log — just a hidden AI surface, which is also what a seat-less caller and a Community Edition deployment correctly see. A reader who believes the servers are bare is one step from "clean this up". Verified: spec 7174 passed (277 files, 7 new), client 205, runtime 956; `check:route-envelope` 0 ratcheted on both surfaces, self-test included; `check:role-word`, `check:nul-bytes` clean; spec and client tsc clean. No behavior change, so no changeset. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZaiYNM5igzGk6JiibjShb
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 116 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The Check Changeset gate takes an empty-frontmatter changeset as the sanctioned "releases nothing" declaration, on par with the skip-changeset label. The diff is comments, a doc-block relocation and one new test file. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZaiYNM5igzGk6JiibjShb
Picks up #4222, which rewrote the sibling refusal in `domains/ai.ts` (mounted-but-unimplemented now answers 501, not 404) and re-pinned `check-route-envelope.mjs`. It deliberately left `GET /ai/agents`'s empty-list 200 alone, so this PR's comment block on that branch merges without conflict — verifying it against the merged tree rather than assuming. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZaiYNM5igzGk6JiibjShb
os-zhuang
marked this pull request as ready for review
July 31, 2026 00:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the remainder of #4053. Companion: objectui#3080.
Where #4053 actually stands
The issue asked for three steps plus one relocation. Checking each rather than assuming:
buildAgentRoutesemits{ success: true, data }runtime/src/domains/ai.tsfallback, same shapeAiAgentsResponseSchemadescribesdata, not the bodyThe SDK row is the one worth stating plainly, because "no diff" reads like an oversight.
unwrapResponsereturnsbody.datawhen a body carries a booleansuccessand adatakey, sobody?.agentsreads.agentsoff{ agents }whichever producer answered. That is true only because the conversion relocated the payload; the flatten would have made this exact line readundefined. The line now says so, instead of leaving the next reader to re-derive it.The declaration follows the wire
AiAgentsResponseSchemawas declared as the wholeGET /api/v1/ai/agentsbody. Since the conversion it is what travels underdata— so it says that now, along with why this was #3843's relocation (a declared payload moving unchanged) rather than #3983's flatten (an ad-hoc{ links }wrapper being reshaped). That reasoning existed only in the two producers that implement it and in a closed PR body; it belongs on the thing both of them import.One pin, in the package both producers share
packages/spec/src/api/ai-agents-envelope.test.tsasserts both halves of the contract —envelopeViolationsclean anddataparsing as the declared payload — with the drifts pinned as negatives: the flattened conversion, the pre-#4053 bare body, anagentskey mirrored besidedata(the #4038/#4049 shim), and a row missingcapabilities.Note which check catches the flatten:
The envelope is conformant; it is the payload that stops matching its declaration. A suite leading with
envelopeViolationsalone passes that body — which is exactly how it would ship.It lives in
specbecause nothing else can hold it. Each producer pins its own body in its own repo and neither can pin the other's, andscripts/check-route-envelope.mjsreaches neither: dispatcher domains return{ status, body }for a central sender (#4049), and cloud is outside the framework guard entirely. What both producers do share is this package.Records that had gone stale
Four comments across
runtimeandclientstill described cloud as unenveloped and the migration as in flight, and theclientsuite's header said "cloud's has not yet [converted]".On this route that is not cosmetic. The entire reason #4053 exists is that a wrong belief about the shape produces no error, no 403 and no log — just a hidden AI surface, which is also what a seat-less caller (ADR-0068) and a Community Edition deployment correctly see. A reader who believes the servers are still bare is one step from "clean this up".
The
clientsuite keeps reading the unenveloped body, retitled to say why: an SDK outlives the server it is pointed at, so a current client talking to a pre-conversion deployment is ordinary, not hypothetical.Verification
Re-run after merging
main(see below), so these are the numbers for the merged tree rather than the branch point:@objectstack/spec@objectstack/client@objectstack/runtimecheck:route-envelope(+ self-test)check:role-word·check:nul-bytestsc --noEmit— spec, clientruntime'stsc --noEmitreports two pre-existingTS6133unused-symbol errors indomains/mcp.tsanddomains/meta.ts— neither file is in this diff, andruntimehas notype-checkscript, so this is not the gate that package ships behind. Recorded rather than quietly skipped.Why
mainis merged in#4222 landed while this was open and edits
packages/runtime/src/domains/ai.ts— the same file, changing the sibling mounted-but-unimplemented refusal from 404 to 501 and re-pinningcheck-route-envelope.mjs. Git reported no conflict, but "no conflict" is not "still correct" when another PR rewrites the same function, somainis merged here and the gates above were re-run against the result. #4222 explicitly leftGET /ai/agents's empty-list 200 alone; the two changes sit side by side. (check:route-envelopenow audits 16 dispatcher domains rather than 15 — #4222's newdomains/unavailable.ts.)Carries an empty changeset — the sanctioned "this PR releases nothing" declaration. There is no behavior change: comments, one doc-block relocation, and a test file.