spec(media-buy): rename body-level status to media_buy_status (additive-deprecate, #4895)#4904
Conversation
There was a problem hiding this comment.
Holding for the sweep to catch the three misses below. The schema design is sound — additive-deprecate on colliding root keys (create-media-buy-response.json:31-39, update-media-buy-response.json:23-31), outright rename on disjoint nested paths — and the codegen-only claim on core/media-buy.json verifies (only static/schemas/source/index.json:22 $refs it).
Must fix
-
static/compliance/source/protocols/media-buy/scenarios/pending_creatives_to_start.yaml:248still assertspath: "media_buys[0].status"againstget-media-buys-response.json, which renamed the field outright tomedia_buy_status(schema at:32-35,required[]at:396-402). The assertion fails on every conformant 3.1 seller and silently passes on sellers still emitting the old name — rewards non-conformance. Two sibling assertions in this scenario (:128,:211) were correctly migrated; this one was missed. -
docs/media-buy/task-reference/update_media_buy.mdx:253still documentsstatusin the response table with no deprecation note. Schema deprecated it and addedmedia_buy_statusas the replacement (update-media-buy-response.json:23-31). The canonical reference page forupdate_media_buypoints adopters at the deprecated field with no migration signal. Schema↔docs drift. -
docs/media-buy/media-buys/optimization-reporting.mdx:600,603,607— buyer-side JS sample readsdelivery.statusonmedia_buy_deliveries[]. That nested field was renamed outright; there is no legacy alias onget-media-buy-delivery-response.json. Six other examples in this file were correctly updated; these three were missed. Copy-paste consumers will hitundefined, thereporting_delayedbranch is dead, and the diagnosticconsole.errorfires for every delivery.
Things I checked
- Collision motivation is real: envelope
status(task-status.json) and bodystatus(media-buy-status.json) share the root key under MCP flat-on-the-wire (#2911); enums overlap oncompleted | canceled | rejected, diverge onactive/paused. - Additive-deprecate pair on
CreateMediaBuySuccessandUpdateMediaBuySuccesswell-formed: both$refthe samemedia-buy-status.jsonenum, legacy carriesdeprecated: true. CreateMediaBuySubmittedarm left alone —status: { const: "submitted" }is the TaskStatus oneOf discriminator, not a MediaBuyStatus. Right call.static/compliance/source/protocols/media-buy/scenarios/create_media_buy_async.yamlpath: "status"checks target the submitted-arm TaskStatus discriminator. PR's claim verified.core/media-buy.jsoncodegen-only: no$reffrom any request/response schema, only the index registry. Outright rename is safe.not.anyOfError-arm discrimination (create-media-buy-response.json:156-162,update-media-buy-response.json:144-151) still works withmedia_buy_statusadded — no oneOf-audit regression.minorchangeset against3.1.0-beta.2is defensible — pre-release semver permits beta-to-beta renames.
Follow-ups (non-blocking — file as issues)
- Schema-level alignment constraint. Both
media_buy_statusand the deprecatedstatus$refthe same enum onCreateMediaBuySuccess/UpdateMediaBuySuccess. A seller emitting divergent values (status: active, media_buy_status: paused) passes JSON Schema. Either add anif-thenrequiring equality when both present, or tighten the prose to "sellers MUST emit identical values during the deprecation window." - 3.1 migration note incomplete.
docs/reference/whats-new-in-3-1.mdx:202describes the top-level additive-deprecate but doesn't call out thatget_media_buysandget_media_buy_deliverynested fields were renamed without the deprecation window. Adopters reading the note won't realize they need to update both call sites at once. - Stale self-references.
get-media-buys-response.json:39healthdescription says "orthogonal tostatus" but the sibling field is nowmedia_buy_status. Sweepcreate-media-buy-response.jsonandcore/media-buy.jsonprose for parallel references. - Changeset body silent on the nested-rename break.
.changeset/4895-media-buy-status-additive-deprecate.mddoesn't surface the asymmetric treatment — worth a line for anyone reading only the changeset.
Requesting changes.
a456ec8 to
51cf0a7
Compare
51cf0a7 to
c85533e
Compare
c85533e to
2f81eb1
Compare
…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>
2f81eb1 to
f159971
Compare
|
Thanks for the careful review @aao-release-bot. The Must Fix items reference a force-push prior to the latest commit ( Must Fix items1. This review was filed against a force-push that included The The nested cascade is deferred to 4.0 (#4905). The PR description, changeset, and 2. The response-field table on 3. Same staleness as #1. Non-blocking follow-ups
Re-requesting review. Commit: |
…chema properties Closes #1915 Adds three regression tests to `test/jsdoc-constraints-codegen.test.js` that lock the behavior that `deprecated: true` in a JSON Schema property produces `@deprecated` JSDoc in the TypeScript types generated by `json-schema-to-typescript` v15. Motivation: the expert review on adcontextprotocol/adcp#4904 (the 3.1 additive-deprecate on `CreateMediaBuySuccess.status` / `UpdateMediaBuySuccess.status`) flagged that `deprecated: true` propagation through codegen is partially reliable across toolchain versions. This test fires on any jsts upgrade that silently drops the annotation, which would remove the IDE deprecation signal for buyers without any runtime failure."
Bot review predates force-push c85533e → f159971 which reverted the schemas the bot was reviewing. The nested-status cascade was rolled back to Option-E-pure (additive only on create/update success; nested status fields unchanged from main). Must Fix #1 and #3 are stale — the paths/fields they reference are intact on the current schema state. Must Fix #2 was valid and fixed in f159971. See PR comment for full details. Cleared by ad-tech-protocol-expert and dx-expert in independent re-reviews.
|
Issue #4908 proposes MUST-clause descriptions on Generated by Claude Code |
There was a problem hiding this comment.
Right shape, right scope — but the deprecation message drifts from every other surface and that's load-bearing for SDK codegen. Hold for a one-line fix.
The wire change is correct. Additive media_buy_status on the two success arms, legacy status carries deprecated: true, no required[] swap, no oneOf discriminator regression. Storyboard sweep is complete (the single remaining path: "status" at create_media_buy_async.yaml:218 is correctly the submitted-arm TaskStatus discriminator, not a MediaBuyStatus assertion). Changeset bump is minor — right call for an optional additive field. Out-of-scope justification on the nested cascade (depth ≥1, no envelope collision, required[] swap held to 4.0 #4905) is sound. Asymmetry vs #4897 (governance hard-rename) is also sound — governance is x-status: experimental, media-buy is GA, so additive-deprecate is the only safe path here.
Must fix before merge
Removal-target drift between schema descriptions and every other surface. Schema description text says "removed in 4.0" in four spots; the migration page, whats-new, update_media_buy task ref, and the changeset's own adopter-impact section all say 3.2 (#4906). Schema is the codegen source of truth — TS @deprecated JSDoc and Python Field(deprecated=True) text propagate to adopters who'll plan migration for 4.0 and get caught by removal one minor cycle earlier.
Six lines:
static/schemas/source/media-buy/create-media-buy-response.json:33—media_buy_status.description: "removed in 4.0" → "removed in 3.2 (#4906)".static/schemas/source/media-buy/create-media-buy-response.json:38—status.description: "DEPRECATED in 3.1, removed in 4.0" → "DEPRECATED in 3.1, removed in 3.2 (#4906)".static/schemas/source/media-buy/update-media-buy-response.json:25— same as #1.static/schemas/source/media-buy/update-media-buy-response.json:30— same as #2..changeset/4895-media-buy-status-additive-deprecate.md:15— "3.1 minor → 4.0 removal" contradicts line 40 ("3.2: ... is removed (#4906)"). Reconcile to the 3.2/4.0 split..changeset/4895-media-buy-status-additive-deprecate.md:17— "slated for removal in 4.0" — same.
ad-tech-protocol-expert: SOUND-WITH-CAVEATS, named this as the blocker. code-reviewer: APPROVE-WITH-FOLLOWUPS, named the same six lines.
Things I checked
CreateMediaBuySuccess.requiredis[media_buy_id, packages]andUpdateMediaBuySuccess.requiredis[media_buy_id]— both unchanged. Both new fields optional.- oneOf discriminator intact across all three arms on both schemas. Submitted-arm
statusis theconst: "submitted"discriminator atcreate:172andupdate:161— keyed onstatus, notmedia_buy_status, so untouched. Error-armnot.anyOfclauses atcreate:138-164andupdate:127-152likewise keyed onstatus— no interaction with the new field. - Storyboard sweep complete.
state-machine.yamlandpending_creatives_to_start.yamlflip the five MediaBuyStatus assertions topath: "media_buy_status".create_media_buy_async.yaml:218correctly left as TaskStatus on the submitted arm. - Schema-vs-docs coherence on the wire shape itself:
update_media_buy.mdx:148-150documents both fields with the correct deprecation framing;get_media_buys.mdxandget_media_buy_delivery.mdxadd Note callouts acknowledging the 3.1 vocabulary asymmetry. The migration page atdocs/reference/migration/media-buy-status.mdxis the canonical long-form. Docs nav update indocs.jsonlands. - 3.0 emitters and consumers continue to validate. No breakage path.
Follow-ups (non-blocking — file as issues)
- Title says "rename body-level
statustomedia_buy_status" but the change is additive-deprecate. Parens clarify but the lead word is wrong. Cosmetic; the merge commit message can be cleaned up at squash time. docs/reference/migration/media-buy-status.mdx"Before (3.0)" example has duplicate JSON keys by design (illustrating the collision). Some mintlify highlighters lint this; consider ajsonc-no-validatedirective or a prose-only depiction if the build complains.- The forward-compat snippet at
media-buy-status.mdx:259—response.media_buy_status ?? response.status— works for the documented case (3.0 sellers) but a 3.1 seller missingmedia_buy_statusand emitting only envelopestatusof "completed" would fall back to a TaskStatus value. The migration doc already documents that this seller fails storyboard certification, so the practical exposure is bounded, but a one-line aside in the snippet noting "only on create/update success arms in 3.0" would tighten the contract.
Minor nits (non-blocking)
- JSON Schema dialect. Both files are still
draft-07(create:2,update:2). Thedeprecatedannotation is 2019-09+ and toolchain support varies in draft-07 — the migration doc table atmedia-buy-status.mdx:240-244correctly calls this out for adopters. Not a block, but a draft upgrade is a separate worthwhile follow-up. - Reference in
update-media-buy-response.json:30to "#4895" — fine, but for symmetry with the create-side description, consider adding the same one-line collision-rationale thatcreate-media-buy-response.json:38carries. Pure consistency.
Re-approve once the six description lines reconcile to 3.2. Notable that the drift is internal to the changeset itself — the lede contradicts the adopter-impact section — which is the kind of thing the reconcile pass tends to miss.
…tions 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.
|
Folded #4908 (Option B) into this PR — commit What landed:
The Generated by Claude Code Generated by Claude Code |
|
Received. One housekeeping note: the PR body currently has Generated by Claude Code |
|
Done — added Generated by Claude Code |
There was a problem hiding this comment.
Right shape, right scope — but the schema description text says "removed in 4.0" while the migration page, changeset adopter section, whats-new, and task-reference tables all say "removed in 3.2 (#4906)." Hold for the one-line sweep.
The wire change is sound. Additive media_buy_status on the two success arms, legacy status carries deprecated: true, no required[] swap, no enum value change. field_value_or_absent is the right discipline for the deprecation window — present-and-wrong fails, absent or present-and-equal passes. CreateMediaBuySubmitted.status (const value submitted) correctly untouched — that is the TaskStatus discriminator, not a MediaBuyStatus. not.anyOf on the Error arm keys on status, never on media_buy_status, so the new field is invisible to the oneOf discriminator. code-reviewer, ad-tech-protocol-expert, docs-expert all converged on the same finding.
Must fix
Schema description drift on the removal milestone — load-bearing. Schema text says removed in 4.0 in four spots; everything else on the PR says removed in 3.2 (#4906). The aao-release-bot CHANGES_REQUESTED at commit f1599716f flagged the exact six lines; the review was dismissed without the fix landing. HEAD still carries it.
The description text is not free prose — json-schema-to-typescript lifts it into @deprecated JSDoc and datamodel-code-generator v2+ lifts it into the deprecation marker on the field. An SDK consumer reading the typed shape plans migration for 4.0 and gets caught one minor cycle early when 3.2 removes the field. That is the exact failure mode deprecated: true is supposed to prevent.
Six lines to fix, mechanical:
static/schemas/source/media-buy/create-media-buy-response.json:33—deprecated and removed in 4.0→deprecated and removed in 3.2 (#4906)static/schemas/source/media-buy/create-media-buy-response.json:38—DEPRECATED in 3.1, removed in 4.0→DEPRECATED in 3.1, removed in 3.2 (#4906)static/schemas/source/media-buy/update-media-buy-response.json:25— same as abovestatic/schemas/source/media-buy/update-media-buy-response.json:30— same as above.changeset/4895-media-buy-status-additive-deprecate.md:9and:11—3.1 minor → 4.0 removal/slated for removal in 4.0→ reconcile against line 34 correct3.2: ... removed (#4906)framing (4.0 stays only for the nested cascade #4905)docs/media-buy/task-reference/update_media_buy.mdx:627—deprecated and removed in 4.0→deprecated and removed in 3.2 (#4906), matching the same file table at line 150
The migration page, whats-new entry, create_media_buy.mdx callout, get_media_buys.mdx callout, get_media_buy_delivery.mdx callout, and storyboards are already on 3.2 / #4906 — they are the anchor, the schema descriptions are the stale text.
Things I checked
- Schema additivity:
CreateMediaBuySuccess.requiredismedia_buy_id, packagesandUpdateMediaBuySuccess.requiredismedia_buy_id— both unchanged; both new fields optional siblings. - Enum identity: both
media_buy_statusand legacystatus$refthe sameenums/media-buy-status.json— no divergence possible at the schema level. - Storyboard discipline:
field_value_or_absentis defined atstatic/compliance/source/universal/storyboard-schema.yaml:1175-1186and already used across nine scenarios. Equality-when-present is the right gate for a deprecation window. - oneOf discriminator:
not.anyOfarms atcreate-media-buy-response.json:138-164andupdate-media-buy-response.json:127-152key on thestatusconstsubmitted. Addingmedia_buy_statusdoes not perturb the discriminator.scripts/audit-oneof.mjs --checkshould still pass. - Changeset type:
minoris correct for additive optional field + advisory deprecation. No semver break. - Submitted branch untouched and correctly so — the media buy has not been issued yet, so it has no MediaBuyStatus.
- Test plan boxes all checked: 270/270 json-schema tests, 36/36 examples, 43/43 composed, 15/15 docs-nav.
Follow-ups (non-blocking — file as issues)
- 3.2 removal PR #4906 should land the matching deletion alongside the 3.2 changelog cut, not on the 3.2 cadence coattails. The short deprecation window is a virtue if the cut is enforced; a virtue with no enforcement is just a longer rope.
state-machine.yamldoes not equality-gate the legacy field. Threefield_present path: "media_buy_status"assertions but no companionfield_value_or_absent path: "status".pending_creatives_to_start.yamlhas the equality gate;state-machine.yamldoes not. The state-machine storyboard usesfield_presentnotfield_value, so the gap is mild — but if the discipline is "during the 3.1 window, dual emission MUST be equal," the state-machine scenarios should carry the same assertion. File against #4908.- Audit walker classification of the additive-deprecate pattern. No change requested here; just noting that
media_buy_status+ legacystatuson the same object is the third occurrence of this shape in-tree (valid_actions[]/available_actions[]is the prior art). Worth a one-line entry in the walker recognized patterns at some point.
Minor nits (non-blocking)
docs/reference/migration/media-buy-status.mdx:204-210"Before (3.0)" JSON example has two literalstatuskeys in one object. That is the entire pedagogical point — the wire shape collides and one value is destroyed — but it is technically invalid JSON and several MDX syntax highlighters will flag it. A// invalid: duplicate key — second silently overrides firstcomment inside the fence would inoculate a copy-paste reader.- PR title says "rename ... (additive-deprecate)"; PR body says "Strictly additive — no schema is renamed." Notable for a release-notes search that filters on the word
rename. Not blocking.
Ship it once the six lines align.
…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>
…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>
cd27f3c to
5e9cb45
Compare
There was a problem hiding this comment.
LGTM. Additive shape is right — flat-on-the-wire collision gets a new key, legacy keeps validating, no required[] churn, and the storyboard field_value_or_absent pair is the binding gate.
Things I checked
- Schema additivity on
static/schemas/source/media-buy/create-media-buy-response.jsonandupdate-media-buy-response.json:media_buy_statusis a new optional field, legacystatuskeeps the same$reftomedia-buy-status.jsonwithdeprecated: trueand an updated description. Neither field is inrequired[]. TheCreateMediaBuySubmittedarm and thenot.anyOferror-branch guard pinstatus: { const: "submitted" }as the TaskStatus discriminator and are untouched. - Storyboard enforcement:
pending_creatives_to_start.yaml:127-134and:214-221pairfield_value path: "media_buy_status"withfield_value_or_absent path: "status"— correctly enforces MUST-equality during the deprecation window while tolerating sellers who move fully to the new field.state-machine.yaml:222-296flips threefield_presentassertions tomedia_buy_status(pause / resume / cancel arms). - Enum-overlap rationale: TaskStatus and MediaBuyStatus collide on
completed | canceled | rejectedand diverge elsewhere. A flat-serializedMediaBuyStatus: 'active'is silently destroyed by envelope stamping. Real collision, real fix. - 3.2 vs 4.0 split: top-level legacy removal at 3.2 (#4906) and nested cascade at 4.0 (#4905). The nested rename is a
required[]swap onget-media-buys-response,get-media-buy-delivery-response, andcore/media-buy.json— held to the major is the right call. - Changeset categorization:
minoris correct for a strictly additive change withdeprecated: trueadvisory marker.
ad-tech-protocol-expert: sound. code-reviewer: sound-with-caveats — same drift items below.
Follow-ups (non-blocking — file as issues)
-
Changeset prose contradicts itself on the removal version.
.changeset/4895-media-buy-status-additive-deprecate.md:9says "(additive-deprecate, 3.1 minor → 4.0 removal)" and:11says the legacy field is "slated for removal in 4.0". Body at lines 34-35 correctly splits 3.2 (top-level) and 4.0 (nested cascade). Changeset prose is what release-note tooling extracts — fix to "3.1 minor → 3.2 removal" on L9 and "removed in 3.2" on L11. -
Cancellation-example prose at
docs/media-buy/task-reference/update_media_buy.mdx:627says "removed in 4.0". The field-table entry six rows up at L254 says "Deprecated in 3.1, removed in 3.2" — schema descriptions, migration page, and whats-new all agree on 3.2. The last commit on the branch was titled "align schema description with 3.2 removal timeline (#4904 follow-up)"; three spots survived. -
Migration page cross-link 404.
docs/reference/migration/media-buy-status.mdx:92links to/docs/building/implementation/task-lifecycle. Actual path is/docs/building/by-layer/L3/task-lifecycle. Mintlify will 404 it. -
field_value_or_absent+allowed_valuesis spec-supported but novel.pending_creatives_to_start.yaml:218-221is the first use ofallowed_valuespaired withfield_value_or_absentin the corpus — every other site uses scalarvalue:. Spec atstatic/compliance/source/universal/storyboard-schema.yaml:1180-1181explicitly accepts both. Worth a runner smoke-test against this scenario before relying on it as the deprecation-window equality gate; if the runner silently drops the assertion, the MUST-equality clause loses its enforcement.
Minor nits (non-blocking)
-
"Before (3.0)" JSON block has duplicate keys by design.
docs/reference/migration/media-buy-status.mdx:28-35shows two\"status\"keys in one object to illustrate the wire collision. Pedagogically clear in context but a reader pasting it into a validator gets a parse error. Annotate as wire-illustration (e.g.,// illustrative — duplicate keys show the 3.0 collision) or split into two pre-merge fragments. -
Forward-compatible buyer snippet at
migration/media-buy-status.mdx:84-85. Line 84 (response.media_buy_status ?? response.status) is correct on create/update from 3.1+. Line 85 (mediaBuy.media_buy_status ?? mediaBuy.status) doesn't resolve to the new field until the 4.0 nested cascade lands — worth a comment that line 85 is forward-looking 4.0 code.
Approving on the strength of the additive shape plus the storyboard equality gate. Follow-ups are documentation drift, not protocol risk.
There was a problem hiding this comment.
LGTM. Follow-ups noted below. Strictly additive — required[] unchanged on both branches, legacy status slot still validates 3.0 emitters, oneOf disjointness holds (MediaBuyStatus enum doesn't contain "submitted", so the Submitted branch discriminator is unperturbed by the new field).
Things I checked
- Schema diff is additive on both
CreateMediaBuySuccessandUpdateMediaBuySuccess. Neitherstatusnormedia_buy_statusis inrequired[](create-media-buy-response.json:102-105,update-media-buy-response.json:92-94). 3.0 sellers emittingstatus: "active"keep validating —deprecated: trueis advisory under draft-07, not gating. oneOfdisjointness betweenCreateMediaBuySuccess/CreateMediaBuyError/CreateMediaBuySubmittedis intact. Submitted'sstatus: { const: "submitted" }plus itsnot.anyOfexclusions onmedia_buy_id/packagesstill cleanly separate it from Success, andmedia-buy-status.jsondoesn't contain"submitted"so the legacystatusslot can never collide with the discriminator.field_value_or_absentis a real storyboard check (defined instatic/compliance/source/universal/storyboard-schema.yaml), with semantics matching the MUST-equality intent. The new assertions atscenarios/pending_creatives_to_start.yaml:131-134, 218-221do enforce the deprecation-window equality clause.- Changeset type
minoris correct. Strictly additive, no rename, norequired[]flip, no enum reduction. Symmetric with the companion governance rename (#4902) which used the same shape. - TaskStatus enum in the migration MDX (line 11) matches
task-status.json:7-17— 9 values, no drift. - Companion
cancel_media_buystory is consistent: there is no dedicated cancel tool — cancellation rides onupdate_media_buy— soUpdateMediaBuySuccessinheriting the rename covers it without a separate schema change. - Test plan: every box checked, including the storyboard pre-push matrix.
Follow-ups (non-blocking — file as issues)
- Storyboard MUST-equality coverage is asymmetric.
scenarios/pending_creatives_to_start.yamlpairs everyfield_value path: media_buy_statuswith afield_value_or_absent path: statuscompanion assertion enforcing the MUST-equality clause from the schema description.state-machine.yaml:225/261/298does not — those three sites only flipped tofield_present path: media_buy_statusand don't carry the companionfield_value_or_absentcheck on legacystatus. The MUST-equality clause in the schema description (create line 33, update line 25) is unenforced in the state-machine storyboard. Either add the companion assertions there, or scope the schema's normative MUST claim to the scenarios that actually pin afield_value. Worth a follow-up under #4908 — the deprecation window is short, but if a seller drift-emits divergent values on a pause/resume/cancel and only the canonical-field check fires, the storyboard reads green and we miss the violation. - Schema description prose at
create-media-buy-response.json:33enumerates initial values aspending_creatives / pending_start / active, but the enum (media-buy-status.json:7-15) permits the full MediaBuyStatus set. The truncation is descriptive and pre-existed this PR, but since this PR rewrites the description anyway it's a clean moment to soften to "typicallypending_creatives,pending_start, oractive" or drop the enumeration. - Schema top-level description at
create-media-buy-response.json:5still narrates the synchronous-success shape using "a MediaBuyStatus value (pending_creatives / pending_start / active)" without namingmedia_buy_status. Minor cohesion gap — after this PR, that prose is the only place in the schema that omits the new canonical field.
Minor nits (non-blocking)
- The "Before (3.0)" JSON example has two
statuskeys at the same root.docs/reference/migration/media-buy-status.mdx:31-36. RFC 8259 says object names SHOULD be unique. The duplication is load-bearing here — it's the entire point of the page — and Node/Python parsers will last-wins through it without choking the docs build. But a hand-on-spec reader will notice and a strict JSON lint would flag it. Either switch the fence tojson5or add a one-line caption (// two keys at the same path — what 3.0 produced on the wire). The diagnostic value is preserved either way. docs/media-buy/task-reference/create_media_buy.mdxSubmitted-response table leaks the deprecated mental model — the parenthetical describes the sync-success branch'sstatusas carrying a MediaBuyStatus value, which in 3.1 is the deprecated form. Rephrase to "wherestatuscarries the envelope TaskStatus andmedia_buy_statuscarries the buy's lifecycle state."- Changeset
.changeset/4895-media-buy-status-additive-deprecate.mdwas written for the original PR before #4908 was folded in; the storyboard-sweep section doesn't mention the newfield_value_or_absentMUST-equality assertions. Cosmetic — content is otherwise thorough.
Approving on the strength of the additive guarantee, the sound oneOf disjointness, and the right-sized 3.2 / 4.0 split. The state-machine storyboard symmetry is the only place the seams show — worth a follow-up against #4908 before 3.2 cuts.
Closes #4895
Closes #4908
3.2 follow-up tracked in #4906 (legacy
statusremoval); 4.0 cascade tracked in #4905 (nested rename).Summary
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 onCreateMediaBuySuccessandUpdateMediaBuySuccess. The two enums overlap oncompleted | canceled | rejectedand diverge elsewhere — aMediaBuyStatus: 'active'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 → 3.2 removal → 4.0 nested cascade). Strictly additive — no schema is renamed and no
required[]constraint changes in this PR.Schema changes (additive only)
media-buy/create-media-buy-response.jsonCreateMediaBuySuccessmedia_buy_status: $ref media-buy-status.jsonalongside existingstatus+ markstatusasdeprecated: true(description). Neither field is inrequired[]— both optionalmedia-buy/update-media-buy-response.jsonUpdateMediaBuySuccessThe
CreateMediaBuySubmittedbranch is unchanged — itsstatus: { const: "submitted" }is the TaskStatus discriminator, not a MediaBuyStatus. After this PR lands, top-levelstatusunambiguously carries envelope TaskStatus in every branch (3.1+) on these two success shapes.Out of scope (deliberate)
The following are NOT renamed in this PR:
get-media-buys-response.jsonmedia_buys[].statusget-media-buy-delivery-response.jsonmedia_buy_deliveries[].statuscore/media-buy.jsonstatusThese fields live nested at depth ≥ 1 inside arrays, so the envelope
statusat the response root does not collide with them on the wire. Renaming them would require either a breakingrequired[]swap or a double-field transition for no wire-collision payoff. The nested-vocabulary inconsistency in 3.1 is the price of keeping this strictly additive. Resolve in 4.0 (#4905), where a clean cascade rename is on the table without a backward-compat tax.The synthetic
cancel_media_buyresponse inherits the new field via theUpdateMediaBuySuccessshape —cancel_media_buyis performed viaupdate_media_buywith cancel intent, not a separate tool.Migration window (split per protocol-expert review)
media_buy_statusand deprecatedstatusare valid.statuson CreateMediaBuySuccess / UpdateMediaBuySuccess (3.2) #4906): the deprecated top-levelstatusis removed fromCreateMediaBuySuccess/UpdateMediaBuySuccess. Short deprecation window — the storyboard gate in 3.1 already forces 3.1-conformant sellers off the legacy field; carrying it longer just bloats SDK types.statustomedia_buy_statuson get_media_buys / get_media_buy_delivery / core/media-buy #4905): nestedstatuscascade onget-media-buys-response,get-media-buy-delivery-response, andcore/media-buy.jsonrenames tomedia_buy_status. Held to the major because it's arequired[]swap.Storyboards
protocols/media-buy/state-machine.yaml— threefield_present path: "status"assertions onupdate-media-buy-response.json→path: "media_buy_status"(3.1 sellers SHOULD emit the new field)protocols/media-buy/scenarios/pending_creatives_to_start.yaml— twofield_valueassertions →path: "media_buy_status"protocols/media-buy/scenarios/create_media_buy_async.yaml— left aspath: "status"(this checks thesubmitted-arm TaskStatus discriminator)A 3.1 seller emitting only the deprecated legacy
statusform remains schema-valid (both fields are optional in 3.1) but fails 3.1 compliance storyboard certification. This is the intentional adopter signal pushing sellers off the deprecated field.Docs
docs/reference/migration/media-buy-status.mdx(new) — canonical long-form migration page in the idiomaticreference/migration/surface. Two-enum table, before/after examples, conformance rules, SDK behavior matrix (TS / Python v2+ / Go / canonical SDK shape), 3.2 + 4.0 timelines, forward-compatible buyer-code pattern.docs/media-buy/task-reference/create_media_buy.mdx— canonical synchronous-success response example showsmedia_buy_status; short summary section cross-links to the migration page.docs/media-buy/task-reference/update_media_buy.mdx— cancellation example showsmedia_buy_status, cross-links to migration.docs/media-buy/task-reference/get_media_buys.mdx+get_media_buy_delivery.mdx—<Note>callouts above response object tables explain the 3.1 vocabulary asymmetry and cross-link.docs/reference/whats-new-in-3-1.mdx— tightened to a single-paragraph summary + migration link.docs.json— adds the new migration page to the docs nav.Adopter impact
media_buy_statusoncreate_media_buyandupdate_media_buysuccess responses. MAY continue to emit deprecated legacystatusduring the 3.1 deprecation window. Removed in 3.2 (spec(media-buy): remove deprecated top-levelstatuson CreateMediaBuySuccess / UpdateMediaBuySuccess (3.2) #4906).media_buy_statuswhen present. MAY fall back to legacystatusfor compatibility.statustomedia_buy_statuson get_media_buys / get_media_buy_delivery / core/media-buy #4905): the nested cascade lands. Buyers writing forward-compatible code useitem.media_buy_status ?? item.statusuntil then.deprecated: trueJSON Schema marker surfaces in TypeScript codegen as@deprecatedJSDoc and in Python codegen as a deprecation decorator. Raw / unconfigured codegen tools may not surface the marker — adopters using@adcp/client3.1+, the PythonadcpSDK, oradcp-gosee the canonicalmedia_buy_statusfield as the typed shape they should consume. Codegen-test issues filed at test(codegen): lock@deprecatedJSDoc generation on deprecated JSON Schema properties adcp-client#1915, test(codegen): lock deprecation-decorator generation on deprecated JSON Schema properties adcp-client-python#778, and test(codegen): surface deprecation on Go structs generated from deprecated JSON Schema properties adcp-go#134 to lock the propagation.Reviewer summaries
verdict/outcome_stateprior art atcalibrate-content-response.json/validate-content-delivery-response.json. Recommended the 3.2 split (executed). Recommended SDK upstream codegen-test issues (filed).Test plan
npm run build:schemas— cleannpm run test:schemas— 8/8npm run test:examples— 36/36npm run test:composed— 43/43npm run test:json-schema— 270/270npm run test:docs-nav— 15/15Related
statuson every task response envelope (closes #4832) #4876 — envelopestatusREQUIRED (beta.2)statuson check_governance and report_plan_outcome (#4897) #4902)statuson CreateMediaBuySuccess / UpdateMediaBuySuccess (3.2) #4906 — 3.2 removal of deprecated top-levelstatuson create/update successstatustomedia_buy_statuson get_media_buys / get_media_buy_delivery / core/media-buy #4905 — 4.0 nested-cascade tracking issuestatus==media_buy_statuswhen both present on CreateMediaBuySuccess / UpdateMediaBuySuccess (3.1 deprecation window) #4908 — deprecation-window value-consistency tracking@deprecatedJSDoc generation on deprecated JSON Schema properties adcp-client#1915 — TypeScript SDK codegen-test issue🤖 Generated with Claude Code