fix(server): stamp envelope status at sync dispatch chokepoint#1898
Merged
Conversation
Adds injectEnvelopeStatusIntoResponse to the finalize() chokepoint in createAdcpServer's tool dispatcher. Stamps `status: "completed"` on structuredContent (and the L2 text fallback) when the handler-projected payload doesn't already carry a top-level status field. Closes the SDK-wide envelope-status gap that #1895's narrow get_adcp_capabilities fix didn't reach: projectSync returns the wire payload verbatim and 19 of the 21 per-tool wrap helpers plus the genericResponse fallback (~30 governance / SI / brand tools) built structuredContent with no envelope status. The storyboard step v3_envelope_integrity/no_legacy_status_fields is scoped to get_adcp_capabilities today; @kapoost's one-tool failure masked the SDK-wide gap. Chokepoint placement preserves the MediaBuyStatus / TaskStatus collision at the same top-level key for the 3 affected helpers (mediaBuyResponse, updateMediaBuyResponse, cancelMediaBuyResponse) — the spec-level ambiguity is filed separately. Error envelopes (isError: true) are skipped; submitted envelopes keep their handler-set `status: 'submitted'`. Tracks adcp-client#1897. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 21, 2026
There was a problem hiding this comment.
LGTM. Chokepoint at finalize() is the right seam — one place, every framework-registered tool inherits envelope conformance, no per-helper churn. Follow-ups below.
Things I checked
- Chokepoint placement at
create-adcp-server.ts:3459— ordered aftersanitizeAdcpErrorEnvelope/enrichErrorTwoLayer, before context/version injection. Same row as the other envelope-shape stampers. Correct. - Guard logic at
create-adcp-server.ts:2682:if ('status' in sc) returnpreservesCreateMediaBuySuccess.status: 'active'(test attest/server-create-adcp-server.test.js:476-497locks it in),cancelMediaBuyResponse's hard-coded'canceled', and the submitted-envelope'submitted'.'status' in scis the same shape the sibling injectors use — consistency wins. isError: trueskip is correct for this injector's scope. The broader error-envelope conformance gap (see Follow-ups) lives on theadcpError()builder, not here.- L2/L3 dual write mirrors
injectContextIntoResponseandinjectVersionIntoResponsebyte-for-byte. Drift onJSON.parsefailure is the same drift those two already accept — handlers don't emit non-JSON L2 text alongside L3 structuredContent in practice. - Changeset present,
patchlevel — restoring spec-required envelope behavior, no new exported API. Right call. - Test coverage: four targeted paths (
productsResponse/getSignalsResponse/genericResponsefallback / MediaBuyStatus collision). The collision test is the load-bearing one — proves payload semantics survive the stamp. ad-tech-protocol-expertverdict: sound-with-caveats. Witness invariant not violated — envelope fields (status,context_id,task_id,replayed,adcp_version,errors) are framework-owned perProtocolResponseParser.ts:38-51's own allowlist. Stamping is the framework's job.
Follow-ups (non-blocking — file as issues)
- Error envelopes still don't carry
status: 'failed'/'rejected'.adcpError()atsrc/lib/server/errors.ts:160-164builds{ isError: true, structuredContent: { adcp_error: filtered } }with no top-levelstatus. The PR comment frames this as 'wired separately by the adcp_error path' — today that path doesn't exist. AdCP #4876 makes envelopestatusrequired on every task response, andfailed/rejectedare validTaskStatusvalues. Either extend the chokepoint to derive status fromadcp_error.recovery/code, or land a sibling injector on the error path. Tighten the changeset prose either way — today it overstates the coverage. - Stamp runs unconditionally across the wire-version compat matrix.
injectVersionIntoResponsegates onif (!servedVersion) return;.injectEnvelopeStatusIntoResponsedoes not.COMPATIBLE_ADCP_VERSIONSinsrc/lib/version.ts:28-58still covers v2.5/v2.6/3.0.0-beta.* responses. Pre-3.0 schemas don't define envelopestatusat the top level — additive but ambiguous to a v2.5 buyer. Mirror the version gate (bundleSupportsEnvelopeStatus(adcpVersion)) so we don't ship an envelope field on bundles whose contract didn't include it. 'status' in sctreatsstatus: undefinedas handler-owned. Edge case — a buggy handler that explicitly setsstatus: undefinedblocks the stamp becauseinmatches present-but-undefined.sc.status != nullwould be tighter without losing the documented intent. Cheap to harden.
Minor nits (non-blocking)
- Test reparenting.
test/server-create-adcp-server.test.js:435closesresponse builder wiringearly;:437opens the newenvelope statusdescribe;:560closes it. That sweeps'uses generic wrapper for tools without dedicated builders'(:499),'passes through adcpError responses'(:512), and'detects build_creative single vs multi-format'(:532) into a describe block whose theme they don't share. Tests pass, but the CI output and a future reader will both lie. Move the closing});from:560up to immediately after the MediaBuyStatus collision test at:497. - Orphaned JSDoc at
create-adcp-server.ts:2645-2655. That block describedinjectVersionIntoResponse. Inserting the new function between them moved the JSDoc onto the wrong target — TypeScript binds the new function instead. Lift the 2645-2655 block down next toinjectVersionIntoResponseat:2700, leaving only the new envelope-status JSDoc above the new function. - Notable that #1895 framed itself as the fix and this PR is the third commit in a row cleaning up the same envelope gap. The audit-vs-sweep split is fine for changelog clarity; an
npm run review:fork-matrix-style assertion that every*Responsehelper round-trips an envelopestatuswould close this category for good.
Safe to merge. Land the error-path stamp + version gate in the next sweep — preferably before the storyboard generalises no_legacy_status_fields past get_adcp_capabilities.
7 tasks
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
May 21, 2026
…pdate success responses (#4895) Under MCP flat-on-the-wire serialization, the envelope task-status (`status`, TaskStatus) and the body-level media-buy lifecycle status (`status`, MediaBuyStatus) share the same root key on `CreateMediaBuySuccess` and `UpdateMediaBuySuccess`. Enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus is silently destroyed when the envelope stamps a TaskStatus at the same path. No validator catches it. WG-recommended Option E (additive-deprecate, 3.1 minor → 4.0 removal). **Strictly additive — no schema is renamed and no `required[]` constraint changes.** - create-media-buy-response.json `CreateMediaBuySuccess`: adds `media_buy_status: $ref media-buy-status.json` alongside the existing `status`. The legacy `status` is marked `deprecated: true` and slated for removal in 4.0. Neither field is in `required[]` (both optional in 3.1). `CreateMediaBuySubmitted` branch unchanged — its `status: { const: "submitted" }` is the TaskStatus discriminator. - update-media-buy-response.json `UpdateMediaBuySuccess`: symmetric. Out of scope (deliberate): `get-media-buys-response.json` `media_buys[].status`, `get-media-buy-delivery-response.json` `media_buy_deliveries[].status`, and `core/media-buy.json` `status` are NOT renamed. These fields live nested at depth ≥ 1 inside arrays, so the envelope `status` at the response root does not collide with them on the wire. Renaming them would require either a breaking `required[]` swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 (one buyer call returns `media_buy_status` at root, the next returns `status` inside an array) is the price of keeping this strictly additive. Resolve in 4.0 alongside the legacy-`status` removal, when a clean cascade rename is on the table. `cancel_media_buy` is performed via update_media_buy with cancel intent — there is no dedicated cancel tool. Inherits the rename from UpdateMediaBuySuccess; no separate handling. Storyboards swept: - protocols/media-buy/state-machine.yaml — three field_present path:"status" assertions on update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions checking MediaBuyStatus values on create-media-buy-response.json / update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path:"status" (this checks the submitted-arm TaskStatus discriminator). Docs: - task-reference/update_media_buy.mdx — cancellation success example shows the canonical media_buy_status form. - reference/whats-new-in-3-1.mdx — migration note in Final-spec clarifications batch. Adopter impact: - Sellers (3.1+) SHOULD emit media_buy_status on create/update success; MAY continue emitting the deprecated legacy `status` during the window. - Buyers (3.1+) MUST prefer media_buy_status when present; MAY fall back to legacy `status` for compatibility. - 3.0 sellers and buyers continue to work unchanged. No required-field swap, no rename, no breakage. - 4.0: deprecated top-level `status` removed from create/update success branches. The nested `status` fields on get-media-buys-response items, get-media-buy-delivery-response items, and core/media-buy.json should be addressed in the same 4.0 release as a coherent cascade. - SDK regen required for @adcp/client, adcp-go, Python. Related: - #4876 — envelope status REQUIRED (beta.2) - #4897 — companion governance schema rename (separate PR #4902) - adcontextprotocol/adcp-client#1898 — SDK-side audit + transport precedence Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
May 21, 2026
…pdate success responses (#4895) Under MCP flat-on-the-wire serialization, the envelope task-status (`status`, TaskStatus) and the body-level media-buy lifecycle status (`status`, MediaBuyStatus) share the same root key on `CreateMediaBuySuccess` and `UpdateMediaBuySuccess`. Enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus is silently destroyed when the envelope stamps a TaskStatus at the same path. No validator catches it. WG-recommended Option E (additive-deprecate, 3.1 minor → 4.0 removal). **Strictly additive — no schema is renamed and no `required[]` constraint changes.** - create-media-buy-response.json `CreateMediaBuySuccess`: adds `media_buy_status: $ref media-buy-status.json` alongside the existing `status`. The legacy `status` is marked `deprecated: true` and slated for removal in 4.0. Neither field is in `required[]` (both optional in 3.1). `CreateMediaBuySubmitted` branch unchanged — its `status: { const: "submitted" }` is the TaskStatus discriminator. - update-media-buy-response.json `UpdateMediaBuySuccess`: symmetric. Out of scope (deliberate): `get-media-buys-response.json` `media_buys[].status`, `get-media-buy-delivery-response.json` `media_buy_deliveries[].status`, and `core/media-buy.json` `status` are NOT renamed. These fields live nested at depth ≥ 1 inside arrays, so the envelope `status` at the response root does not collide with them on the wire. Renaming them would require either a breaking `required[]` swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 (one buyer call returns `media_buy_status` at root, the next returns `status` inside an array) is the price of keeping this strictly additive. Resolve in 4.0 alongside the legacy-`status` removal, when a clean cascade rename is on the table. `cancel_media_buy` is performed via update_media_buy with cancel intent — there is no dedicated cancel tool. Inherits the rename from UpdateMediaBuySuccess; no separate handling. Storyboards swept: - protocols/media-buy/state-machine.yaml — three field_present path:"status" assertions on update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions checking MediaBuyStatus values on create-media-buy-response.json / update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path:"status" (this checks the submitted-arm TaskStatus discriminator). Docs: - task-reference/update_media_buy.mdx — cancellation success example shows the canonical media_buy_status form. - reference/whats-new-in-3-1.mdx — migration note in Final-spec clarifications batch. Adopter impact: - Sellers (3.1+) SHOULD emit media_buy_status on create/update success; MAY continue emitting the deprecated legacy `status` during the window. - Buyers (3.1+) MUST prefer media_buy_status when present; MAY fall back to legacy `status` for compatibility. - 3.0 sellers and buyers continue to work unchanged. No required-field swap, no rename, no breakage. - 4.0: deprecated top-level `status` removed from create/update success branches. The nested `status` fields on get-media-buys-response items, get-media-buy-delivery-response items, and core/media-buy.json should be addressed in the same 4.0 release as a coherent cascade. - SDK regen required for @adcp/client, adcp-go, Python. Related: - #4876 — envelope status REQUIRED (beta.2) - #4897 — companion governance schema rename (separate PR #4902) - adcontextprotocol/adcp-client#1898 — SDK-side audit + transport precedence Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
May 21, 2026
…pdate success responses (#4895) Under MCP flat-on-the-wire serialization, the envelope task-status (`status`, TaskStatus) and the body-level media-buy lifecycle status (`status`, MediaBuyStatus) share the same root key on `CreateMediaBuySuccess` and `UpdateMediaBuySuccess`. Enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus is silently destroyed when the envelope stamps a TaskStatus at the same path. No validator catches it. WG-recommended Option E (additive-deprecate, 3.1 minor → 4.0 removal). **Strictly additive — no schema is renamed and no `required[]` constraint changes.** - create-media-buy-response.json `CreateMediaBuySuccess`: adds `media_buy_status: $ref media-buy-status.json` alongside the existing `status`. The legacy `status` is marked `deprecated: true` and slated for removal in 4.0. Neither field is in `required[]` (both optional in 3.1). `CreateMediaBuySubmitted` branch unchanged — its `status: { const: "submitted" }` is the TaskStatus discriminator. - update-media-buy-response.json `UpdateMediaBuySuccess`: symmetric. Out of scope (deliberate): `get-media-buys-response.json` `media_buys[].status`, `get-media-buy-delivery-response.json` `media_buy_deliveries[].status`, and `core/media-buy.json` `status` are NOT renamed. These fields live nested at depth ≥ 1 inside arrays, so the envelope `status` at the response root does not collide with them on the wire. Renaming them would require either a breaking `required[]` swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 (one buyer call returns `media_buy_status` at root, the next returns `status` inside an array) is the price of keeping this strictly additive. Resolve in 4.0 alongside the legacy-`status` removal, when a clean cascade rename is on the table. `cancel_media_buy` is performed via update_media_buy with cancel intent — there is no dedicated cancel tool. Inherits the rename from UpdateMediaBuySuccess; no separate handling. Storyboards swept: - protocols/media-buy/state-machine.yaml — three field_present path:"status" assertions on update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions checking MediaBuyStatus values on create-media-buy-response.json / update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path:"status" (this checks the submitted-arm TaskStatus discriminator). Docs: - task-reference/update_media_buy.mdx — cancellation success example shows the canonical media_buy_status form. - reference/whats-new-in-3-1.mdx — migration note in Final-spec clarifications batch. Adopter impact: - Sellers (3.1+) SHOULD emit media_buy_status on create/update success; MAY continue emitting the deprecated legacy `status` during the window. - Buyers (3.1+) MUST prefer media_buy_status when present; MAY fall back to legacy `status` for compatibility. - 3.0 sellers and buyers continue to work unchanged. No required-field swap, no rename, no breakage. - 4.0: deprecated top-level `status` removed from create/update success branches. The nested `status` fields on get-media-buys-response items, get-media-buy-delivery-response items, and core/media-buy.json should be addressed in the same 4.0 release as a coherent cascade. - SDK regen required for @adcp/client, adcp-go, Python. Related: - #4876 — envelope status REQUIRED (beta.2) - #4897 — companion governance schema rename (separate PR #4902) - adcontextprotocol/adcp-client#1898 — SDK-side audit + transport precedence Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
May 21, 2026
…pdate success responses (#4895) Under MCP flat-on-the-wire serialization, the envelope task-status (`status`, TaskStatus) and the body-level media-buy lifecycle status (`status`, MediaBuyStatus) share the same root key on `CreateMediaBuySuccess` and `UpdateMediaBuySuccess`. Enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus is silently destroyed when the envelope stamps a TaskStatus at the same path. No validator catches it. WG-recommended Option E (additive-deprecate, 3.1 minor → 4.0 removal). **Strictly additive — no schema is renamed and no `required[]` constraint changes.** - create-media-buy-response.json `CreateMediaBuySuccess`: adds `media_buy_status: $ref media-buy-status.json` alongside the existing `status`. The legacy `status` is marked `deprecated: true` and slated for removal in 4.0. Neither field is in `required[]` (both optional in 3.1). `CreateMediaBuySubmitted` branch unchanged — its `status: { const: "submitted" }` is the TaskStatus discriminator. - update-media-buy-response.json `UpdateMediaBuySuccess`: symmetric. Out of scope (deliberate): `get-media-buys-response.json` `media_buys[].status`, `get-media-buy-delivery-response.json` `media_buy_deliveries[].status`, and `core/media-buy.json` `status` are NOT renamed. These fields live nested at depth ≥ 1 inside arrays, so the envelope `status` at the response root does not collide with them on the wire. Renaming them would require either a breaking `required[]` swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 (one buyer call returns `media_buy_status` at root, the next returns `status` inside an array) is the price of keeping this strictly additive. Resolve in 4.0 alongside the legacy-`status` removal, when a clean cascade rename is on the table. `cancel_media_buy` is performed via update_media_buy with cancel intent — there is no dedicated cancel tool. Inherits the rename from UpdateMediaBuySuccess; no separate handling. Storyboards swept: - protocols/media-buy/state-machine.yaml — three field_present path:"status" assertions on update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions checking MediaBuyStatus values on create-media-buy-response.json / update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path:"status" (this checks the submitted-arm TaskStatus discriminator). Docs: - task-reference/update_media_buy.mdx — cancellation success example shows the canonical media_buy_status form. - reference/whats-new-in-3-1.mdx — migration note in Final-spec clarifications batch. Adopter impact: - Sellers (3.1+) SHOULD emit media_buy_status on create/update success; MAY continue emitting the deprecated legacy `status` during the window. - Buyers (3.1+) MUST prefer media_buy_status when present; MAY fall back to legacy `status` for compatibility. - 3.0 sellers and buyers continue to work unchanged. No required-field swap, no rename, no breakage. - 4.0: deprecated top-level `status` removed from create/update success branches. The nested `status` fields on get-media-buys-response items, get-media-buy-delivery-response items, and core/media-buy.json should be addressed in the same 4.0 release as a coherent cascade. - SDK regen required for @adcp/client, adcp-go, Python. Related: - #4876 — envelope status REQUIRED (beta.2) - #4897 — companion governance schema rename (separate PR #4902) - adcontextprotocol/adcp-client#1898 — SDK-side audit + transport precedence Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
May 21, 2026
…pdate success responses (#4895) Under MCP flat-on-the-wire serialization, the envelope task-status (`status`, TaskStatus) and the body-level media-buy lifecycle status (`status`, MediaBuyStatus) share the same root key on `CreateMediaBuySuccess` and `UpdateMediaBuySuccess`. Enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus is silently destroyed when the envelope stamps a TaskStatus at the same path. No validator catches it. WG-recommended Option E (additive-deprecate, 3.1 minor → 4.0 removal). **Strictly additive — no schema is renamed and no `required[]` constraint changes.** - create-media-buy-response.json `CreateMediaBuySuccess`: adds `media_buy_status: $ref media-buy-status.json` alongside the existing `status`. The legacy `status` is marked `deprecated: true` and slated for removal in 4.0. Neither field is in `required[]` (both optional in 3.1). `CreateMediaBuySubmitted` branch unchanged — its `status: { const: "submitted" }` is the TaskStatus discriminator. - update-media-buy-response.json `UpdateMediaBuySuccess`: symmetric. Out of scope (deliberate): `get-media-buys-response.json` `media_buys[].status`, `get-media-buy-delivery-response.json` `media_buy_deliveries[].status`, and `core/media-buy.json` `status` are NOT renamed. These fields live nested at depth ≥ 1 inside arrays, so the envelope `status` at the response root does not collide with them on the wire. Renaming them would require either a breaking `required[]` swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 (one buyer call returns `media_buy_status` at root, the next returns `status` inside an array) is the price of keeping this strictly additive. Resolve in 4.0 alongside the legacy-`status` removal, when a clean cascade rename is on the table. `cancel_media_buy` is performed via update_media_buy with cancel intent — there is no dedicated cancel tool. Inherits the rename from UpdateMediaBuySuccess; no separate handling. Storyboards swept: - protocols/media-buy/state-machine.yaml — three field_present path:"status" assertions on update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions checking MediaBuyStatus values on create-media-buy-response.json / update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path:"status" (this checks the submitted-arm TaskStatus discriminator). Docs: - task-reference/update_media_buy.mdx — cancellation success example shows the canonical media_buy_status form. - reference/whats-new-in-3-1.mdx — migration note in Final-spec clarifications batch. Adopter impact: - Sellers (3.1+) SHOULD emit media_buy_status on create/update success; MAY continue emitting the deprecated legacy `status` during the window. - Buyers (3.1+) MUST prefer media_buy_status when present; MAY fall back to legacy `status` for compatibility. - 3.0 sellers and buyers continue to work unchanged. No required-field swap, no rename, no breakage. - 4.0: deprecated top-level `status` removed from create/update success branches. The nested `status` fields on get-media-buys-response items, get-media-buy-delivery-response items, and core/media-buy.json should be addressed in the same 4.0 release as a coherent cascade. - SDK regen required for @adcp/client, adcp-go, Python. Related: - #4876 — envelope status REQUIRED (beta.2) - #4897 — companion governance schema rename (separate PR #4902) - adcontextprotocol/adcp-client#1898 — SDK-side audit + transport precedence Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
May 21, 2026
…ditive-deprecate, #4895) (#4904) * spec(media-buy): additive-deprecate body-level `status` on create / update success responses (#4895) Under MCP flat-on-the-wire serialization, the envelope task-status (`status`, TaskStatus) and the body-level media-buy lifecycle status (`status`, MediaBuyStatus) share the same root key on `CreateMediaBuySuccess` and `UpdateMediaBuySuccess`. Enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus is silently destroyed when the envelope stamps a TaskStatus at the same path. No validator catches it. WG-recommended Option E (additive-deprecate, 3.1 minor → 4.0 removal). **Strictly additive — no schema is renamed and no `required[]` constraint changes.** - create-media-buy-response.json `CreateMediaBuySuccess`: adds `media_buy_status: $ref media-buy-status.json` alongside the existing `status`. The legacy `status` is marked `deprecated: true` and slated for removal in 4.0. Neither field is in `required[]` (both optional in 3.1). `CreateMediaBuySubmitted` branch unchanged — its `status: { const: "submitted" }` is the TaskStatus discriminator. - update-media-buy-response.json `UpdateMediaBuySuccess`: symmetric. Out of scope (deliberate): `get-media-buys-response.json` `media_buys[].status`, `get-media-buy-delivery-response.json` `media_buy_deliveries[].status`, and `core/media-buy.json` `status` are NOT renamed. These fields live nested at depth ≥ 1 inside arrays, so the envelope `status` at the response root does not collide with them on the wire. Renaming them would require either a breaking `required[]` swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 (one buyer call returns `media_buy_status` at root, the next returns `status` inside an array) is the price of keeping this strictly additive. Resolve in 4.0 alongside the legacy-`status` removal, when a clean cascade rename is on the table. `cancel_media_buy` is performed via update_media_buy with cancel intent — there is no dedicated cancel tool. Inherits the rename from UpdateMediaBuySuccess; no separate handling. Storyboards swept: - protocols/media-buy/state-machine.yaml — three field_present path:"status" assertions on update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions checking MediaBuyStatus values on create-media-buy-response.json / update-media-buy-response.json → path:"media_buy_status". - protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path:"status" (this checks the submitted-arm TaskStatus discriminator). Docs: - task-reference/update_media_buy.mdx — cancellation success example shows the canonical media_buy_status form. - reference/whats-new-in-3-1.mdx — migration note in Final-spec clarifications batch. Adopter impact: - Sellers (3.1+) SHOULD emit media_buy_status on create/update success; MAY continue emitting the deprecated legacy `status` during the window. - Buyers (3.1+) MUST prefer media_buy_status when present; MAY fall back to legacy `status` for compatibility. - 3.0 sellers and buyers continue to work unchanged. No required-field swap, no rename, no breakage. - 4.0: deprecated top-level `status` removed from create/update success branches. The nested `status` fields on get-media-buys-response items, get-media-buy-delivery-response items, and core/media-buy.json should be addressed in the same 4.0 release as a coherent cascade. - SDK regen required for @adcp/client, adcp-go, Python. Related: - #4876 — envelope status REQUIRED (beta.2) - #4897 — companion governance schema rename (separate PR #4902) - adcontextprotocol/adcp-client#1898 — SDK-side audit + transport precedence Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(media-buy): fold #4908 — MUST-equality clause + storyboard assertions for deprecation window During the 3.1 deprecation window both `status` (deprecated) and `media_buy_status` (canonical) coexist on CreateMediaBuySuccess / UpdateMediaBuySuccess. The prior schema was silent on whether sellers emitting both fields must carry the same value; JSON Schema validation passed even for divergent values (e.g., status: active, media_buy_status: paused). - Schema descriptions: add MUST-identical-values clause to `status` and `media_buy_status` on both create-media-buy-response.json and update-media-buy-response.json. - Storyboard (pending_creatives_to_start.yaml): add `field_value_or_absent` assertions on the deprecated `status` path at the two steps that already check `media_buy_status` — passes when status is absent (conformant 3.1 sellers), fails only when status is present with a diverging value. - Migration doc: document the equality requirement and explain why if/then JSON Schema constraint was deferred (short window, codegen compat uncertainty, storyboard is sufficient). Resolves #4908. Refs #4895. * spec(media-buy): align schema description with 3.2 removal timeline (#4904 follow-up) The schema descriptions on CreateMediaBuySuccess / UpdateMediaBuySuccess for the deprecated top-level `status` and the new `media_buy_status` both still said "removed in 4.0" while every other surface (migration page, changeset adopter section, whats-new bullet, task-reference tables) was updated to "removed in 3.2 (#4906)" when the cascade was split. aao-release-bot caught the drift. Three replacements per file ("removed in 4.0" → "removed in 3.2 (#4906)") on: - create-media-buy-response.json: media_buy_status description, deprecated status description - update-media-buy-response.json: same Schema↔doc parity restored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Closes #1897 (the SDK-wide gap the #1895 audit missed).
injectEnvelopeStatusIntoResponseat thefinalize()chokepoint increateAdcpServer's tool dispatcher. One seam — every framework-registered tool gets envelopestatus: \"completed\"without per-helper edits, whether it dispatches throughproductsResponse,mediaBuyResponse,genericResponse, or the test-controller bridge'swrap(merged)rewrites.MediaBuyStatus/TaskStatuscollision at the same top-level key — only stamps whenstructuredContent.statusis missing. SoCreateMediaBuySuccess.status: 'active'(validMediaBuyStatus, not a validTaskStatus) ships verbatim; the spec-level ambiguity is filed for adcontextprotocol/adcp separately.isError: true) are skipped — their status semantics (failed/rejected) belong to the adcp_error path. Submitted envelopes keep their handler-setstatus: 'submitted'. Auto-registeredget_adcp_capabilitiescontinues to stamp viacapabilitiesResponsedirectly (bypassesfinalize()); both layers reinforce.Why the chokepoint instead of fixing each helper
19 of 21
*Responsehelpers had the gap, and the ~30wrap: nulltools inTOOL_METAfell through togenericResponsewith the same gap. Touching every helper would be near-mechanical churn and would still miss future helpers added without remembering the envelope. Thefinalize()chokepoint already runssanitizeAdcpErrorEnvelope/enrichErrorTwoLayer/injectContextIntoResponse/injectVersionIntoResponse— envelope status belongs in the same row.Test plan
test/server-create-adcp-server.test.jscovering productsResponse path, getSignalsResponse path, genericResponse fallback (list_property_lists), and the MediaBuyStatus collision case (create_media_buy returningstatus: 'active'ships unchanged)NODE_ENV=test node --test test/server-create-adcp-server.test.js— 97/97NODE_ENV=test node --test test/server-responses.test.js test/server-decisioning-from-platform.test.js test/server-idempotency.test.js— 312/312NODE_ENV=test node --test test/lib/storyboard-validations.test.js test/lib/idempotency.test.js test/lib/capabilities-tools-drift.test.js test/lib/governance-e2e.test.js test/server-decisioning-capability-projections.test.js— 96/96npm testfull suite: 9409/9417 pass; 5 unrelated failures (allv1-canonical-mapping.json not foundin 3.1.0-beta schema cache — confirmed pre-existing on origin/main, not introduced by this change)npm run format:checkcleanFollow-up
Spec-level question for adcontextprotocol/adcp: envelope-level
status(TaskStatus) collides with payload-levelstatus(MediaBuyStatus) at the same top-level key onmediaBuyResponse/updateMediaBuyResponse/cancelMediaBuyResponse. Either nest payload underpayload(matches the normative envelope example inprotocol-envelope.json) or rename the payload field. Tracking issue inbound.🤖 Generated with Claude Code