Skip to content

spec(media-buy): rename body-level status to media_buy_status (additive-deprecate, #4895)#4904

Merged
bokelley merged 3 commits into
mainfrom
bokelley/spec-4895-media-buy-status-rename
May 21, 2026
Merged

spec(media-buy): rename body-level status to media_buy_status (additive-deprecate, #4895)#4904
bokelley merged 3 commits into
mainfrom
bokelley/spec-4895-media-buy-status-rename

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 21, 2026

Closes #4895
Closes #4908

3.2 follow-up tracked in #4906 (legacy status removal); 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 on CreateMediaBuySuccess and UpdateMediaBuySuccess. The two enums overlap on completed | canceled | rejected and diverge elsewhere — a MediaBuyStatus: '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)

Schema Change
media-buy/create-media-buy-response.json CreateMediaBuySuccess Add media_buy_status: $ref media-buy-status.json alongside existing status + mark status as deprecated: true (description). Neither field is in required[] — both optional
media-buy/update-media-buy-response.json UpdateMediaBuySuccess Symmetric

The CreateMediaBuySubmitted branch is unchanged — its status: { const: "submitted" } is the TaskStatus discriminator, not a MediaBuyStatus. After this PR lands, top-level status unambiguously 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.json media_buys[].status
  • get-media-buy-delivery-response.json media_buy_deliveries[].status
  • core/media-buy.json status

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 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_buy response inherits the new field via the UpdateMediaBuySuccess shape — cancel_media_buy is performed via update_media_buy with cancel intent, not a separate tool.

Migration window (split per protocol-expert review)

Storyboards

  • protocols/media-buy/state-machine.yaml — three field_present path: "status" assertions on update-media-buy-response.jsonpath: "media_buy_status" (3.1 sellers SHOULD emit the new field)
  • protocols/media-buy/scenarios/pending_creatives_to_start.yaml — two field_value assertions → path: "media_buy_status"
  • protocols/media-buy/scenarios/create_media_buy_async.yaml — left as path: "status" (this checks the submitted-arm TaskStatus discriminator)

A 3.1 seller emitting only the deprecated legacy status form 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 idiomatic reference/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 shows media_buy_status; short summary section cross-links to the migration page.
  • docs/media-buy/task-reference/update_media_buy.mdx — cancellation example shows media_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

Reviewer summaries

  • ad-tech-protocol-expert (2 passes): GO. Confirmed verdict/outcome_state prior art at calibrate-content-response.json / validate-content-delivery-response.json. Recommended the 3.2 split (executed). Recommended SDK upstream codegen-test issues (filed).
  • dx-expert (2 passes): Consistency score raised from 2/5 to 4/5 after the docs improvements (migration page, canonical examples, cross-link callouts). GO stands.

Test plan

  • npm run build:schemas — clean
  • npm run test:schemas — 8/8
  • npm run test:examples — 36/36
  • npm run test:composed — 43/43
  • npm run test:json-schema — 270/270
  • npm run test:docs-nav — 15/15
  • Pre-push storyboard matrix — passed

Related

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

@aao-release-bot aao-release-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. static/compliance/source/protocols/media-buy/scenarios/pending_creatives_to_start.yaml:248 still asserts path: "media_buys[0].status" against get-media-buys-response.json, which renamed the field outright to media_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.

  2. docs/media-buy/task-reference/update_media_buy.mdx:253 still documents status in the response table with no deprecation note. Schema deprecated it and added media_buy_status as the replacement (update-media-buy-response.json:23-31). The canonical reference page for update_media_buy points adopters at the deprecated field with no migration signal. Schema↔docs drift.

  3. docs/media-buy/media-buys/optimization-reporting.mdx:600,603,607 — buyer-side JS sample reads delivery.status on media_buy_deliveries[]. That nested field was renamed outright; there is no legacy alias on get-media-buy-delivery-response.json. Six other examples in this file were correctly updated; these three were missed. Copy-paste consumers will hit undefined, the reporting_delayed branch is dead, and the diagnostic console.error fires for every delivery.

Things I checked

  • Collision motivation is real: envelope status (task-status.json) and body status (media-buy-status.json) share the root key under MCP flat-on-the-wire (#2911); enums overlap on completed | canceled | rejected, diverge on active/paused.
  • Additive-deprecate pair on CreateMediaBuySuccess and UpdateMediaBuySuccess well-formed: both $ref the same media-buy-status.json enum, legacy carries deprecated: true.
  • CreateMediaBuySubmitted arm 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.yaml path: "status" checks target the submitted-arm TaskStatus discriminator. PR's claim verified.
  • core/media-buy.json codegen-only: no $ref from any request/response schema, only the index registry. Outright rename is safe.
  • not.anyOf Error-arm discrimination (create-media-buy-response.json:156-162, update-media-buy-response.json:144-151) still works with media_buy_status added — no oneOf-audit regression.
  • minor changeset against 3.1.0-beta.2 is defensible — pre-release semver permits beta-to-beta renames.

Follow-ups (non-blocking — file as issues)

  • Schema-level alignment constraint. Both media_buy_status and the deprecated status $ref the same enum on CreateMediaBuySuccess / UpdateMediaBuySuccess. A seller emitting divergent values (status: active, media_buy_status: paused) passes JSON Schema. Either add an if-then requiring 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:202 describes the top-level additive-deprecate but doesn't call out that get_media_buys and get_media_buy_delivery nested 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:39 health description says "orthogonal to status" but the sibling field is now media_buy_status. Sweep create-media-buy-response.json and core/media-buy.json prose for parallel references.
  • Changeset body silent on the nested-rename break. .changeset/4895-media-buy-status-additive-deprecate.md doesn't surface the asymmetric treatment — worth a line for anyone reading only the changeset.

Requesting changes.

EmmaLouise2018
EmmaLouise2018 previously approved these changes 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 bokelley force-pushed the bokelley/spec-4895-media-buy-status-rename branch from 2f81eb1 to f159971 Compare May 21, 2026 16:48
@bokelley
Copy link
Copy Markdown
Contributor Author

Thanks for the careful review @aao-release-bot. The Must Fix items reference a force-push prior to the latest commit (f1599716f9 after rebase onto current main). Status:

Must Fix items

1. pending_creatives_to_start.yaml:248 path: "media_buys[0].status"stale (the path is correct).

This review was filed against a force-push that included get-media-buys-response.json media_buys[].statusmedia_buy_status (with a required[] swap). That cascade was reverted at the user's instruction prior to this review landing — see commit 2f81eb1ce5 (now superseded by f1599716f9 after rebase). The current schema state on get-media-buys-response.json keeps media_buys[].status exactly as it is on main, so the path on line 248 matches.

The required[] block at get-media-buys-response.json:396-402 still lists status, not media_buy_status — confirming the revert.

The nested cascade is deferred to 4.0 (#4905). The PR description, changeset, and whats-new-in-3-1.mdx bullet all explicitly call out the out-of-scope decision.

2. update_media_buy.mdx:253 documents status with no deprecation notevalid, fixed in f1599716f9.

The response-field table on update_media_buy.mdx was missed when I swept canonical examples. Updated to list media_buy_status (canonical 3.1 field, cross-links to the migration page) and call out the legacy status as deprecated in 3.1 / removed in 3.2 (#4906).

3. optimization-reporting.mdx:600,603,607 JS sample reads delivery.statusstale (the field is correct).

Same staleness as #1. get-media-buy-delivery-response.json media_buy_deliveries[].status was not renamed in this PR's final scope. The JS sample's delivery.status access is correct against the schema as it stands on f1599716f9.

Non-blocking follow-ups

  • Schema-level alignment constraint (sellers emitting divergent statusmedia_buy_status values): legitimate concern. The PR's prose says "sellers MAY emit both during the deprecation window" with no equality clause. Open to adding an if/then constraint requiring equality when both are present, or tightening the prose to a MUST. Will file a separate issue rather than pile onto this PR — the schema marker + storyboard gate cover the common case, and the divergent-value edge case is best treated as its own clarification.
  • 3.1 migration note completeness — addressed in f1599716f9. The whats-new bullet now reads "Nested status on get-media-buys-response, get-media-buy-delivery-response, and core/media-buy.json are out of scope here and addressed in the 4.0 cascade (spec(media-buy): 4.0 cascade — rename nested status to media_buy_status on get_media_buys / get_media_buy_delivery / core/media-buy #4905)." The new dedicated migration page at docs/reference/migration/media-buy-status.mdx also explicitly tables the nested-vs-top-level split per surface.
  • Stale self-references — checked. get-media-buys-response.json:38 health description says "orthogonal to status" — that's correct because the sibling status field is the one we kept. No drift on this surface. create-media-buy-response.json and core/media-buy.json prose references to status were swept and (where appropriate) updated to point at media_buy_status.
  • Changeset body surfaces the asymmetric treatment — the current changeset has an "Out of scope" section explaining the depth ≥ 1 / no-collision rationale, plus separate 3.2 and 4.0 paragraphs marking the migration window. Verifies as covered.

Re-requesting review. Commit: f1599716f9. Latest PR description and the new migration page at docs/reference/migration/media-buy-status.mdx are the canonical surfaces.

bokelley added a commit to adcontextprotocol/adcp-client that referenced this pull request May 21, 2026
…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."
@bokelley bokelley dismissed aao-release-bot[bot]’s stale review May 21, 2026 16:50

Bot review predates force-push c85533ef159971 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.

@bokelley
Copy link
Copy Markdown
Contributor Author

Issue #4908 proposes MUST-clause descriptions on status / media_buy_status + a storyboard equality assertion enforcing status == media_buy_status when both are present — same schema and storyboard surface as this PR, ~5-line delta. Consider folding before merge or confirm it's a deliberate follow-up.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

@aao-release-bot aao-release-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. static/schemas/source/media-buy/create-media-buy-response.json:33media_buy_status.description: "removed in 4.0" → "removed in 3.2 (#4906)".
  2. static/schemas/source/media-buy/create-media-buy-response.json:38status.description: "DEPRECATED in 3.1, removed in 4.0" → "DEPRECATED in 3.1, removed in 3.2 (#4906)".
  3. static/schemas/source/media-buy/update-media-buy-response.json:25 — same as #1.
  4. static/schemas/source/media-buy/update-media-buy-response.json:30 — same as #2.
  5. .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.
  6. .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.required is [media_buy_id, packages] and UpdateMediaBuySuccess.required is [media_buy_id] — both unchanged. Both new fields optional.
  • oneOf discriminator intact across all three arms on both schemas. Submitted-arm status is the const: "submitted" discriminator at create:172 and update:161 — keyed on status, not media_buy_status, so untouched. Error-arm not.anyOf clauses at create:138-164 and update:127-152 likewise keyed on status — no interaction with the new field.
  • Storyboard sweep complete. state-machine.yaml and pending_creatives_to_start.yaml flip the five MediaBuyStatus assertions to path: "media_buy_status". create_media_buy_async.yaml:218 correctly left as TaskStatus on the submitted arm.
  • Schema-vs-docs coherence on the wire shape itself: update_media_buy.mdx:148-150 documents both fields with the correct deprecation framing; get_media_buys.mdx and get_media_buy_delivery.mdx add Note callouts acknowledging the 3.1 vocabulary asymmetry. The migration page at docs/reference/migration/media-buy-status.mdx is the canonical long-form. Docs nav update in docs.json lands.
  • 3.0 emitters and consumers continue to validate. No breakage path.

Follow-ups (non-blocking — file as issues)

  • Title says "rename body-level status to media_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 a jsonc-no-validate directive or a prose-only depiction if the build complains.
  • The forward-compat snippet at media-buy-status.mdx:259response.media_buy_status ?? response.status — works for the documented case (3.0 sellers) but a 3.1 seller missing media_buy_status and emitting only envelope status of "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)

  1. JSON Schema dialect. Both files are still draft-07 (create:2, update:2). The deprecated annotation is 2019-09+ and toolchain support varies in draft-07 — the migration doc table at media-buy-status.mdx:240-244 correctly calls this out for adopters. Not a block, but a draft upgrade is a separate worthwhile follow-up.
  2. Reference in update-media-buy-response.json:30 to "#4895" — fine, but for symmetry with the create-side description, consider adding the same one-line collision-rationale that create-media-buy-response.json:38 carries. 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.
@bokelley
Copy link
Copy Markdown
Contributor Author

Folded #4908 (Option B) into this PR — commit dcb3dad9.

What landed:

  • create-media-buy-response.json + update-media-buy-response.json: MUST-identical-values clause added to both status and media_buy_status descriptions. Divergent emission is now explicitly a conformance violation.
  • pending_creatives_to_start.yaml: two field_value_or_absent assertions added on the deprecated status path alongside the existing media_buy_status checks. Passes when status is absent (conformant 3.1 sellers); fails only when status is present with a mismatched value.
  • Migration doc: equality requirement documented; explains why the if/then JSON Schema constraint (Option A) was deferred (short window, codegen compat risk, storyboard gate is sufficient).

The if/then schema constraint from #4908 Option A is not included — the deprecation window is short and the storyboard enforcement is adequate for 3.1. Issue #4908 can close once this PR merges.

Generated by Claude Code


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

Received. One housekeeping note: the PR body currently has Closes #4895 but not Closes #4908 — add that line if you'd like GitHub to auto-close #4908 on merge, or close it manually after.


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

Done — added Closes #4908 to the PR body. Also cross-linked #4908 in the Related section for visibility. GitHub will auto-close it on merge.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

@aao-release-bot aao-release-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:33deprecated and removed in 4.0deprecated and removed in 3.2 (#4906)
  • static/schemas/source/media-buy/create-media-buy-response.json:38DEPRECATED in 3.1, removed in 4.0DEPRECATED in 3.1, removed in 3.2 (#4906)
  • static/schemas/source/media-buy/update-media-buy-response.json:25 — same as above
  • static/schemas/source/media-buy/update-media-buy-response.json:30 — same as above
  • .changeset/4895-media-buy-status-additive-deprecate.md:9 and :113.1 minor → 4.0 removal / slated for removal in 4.0 → reconcile against line 34 correct 3.2: ... removed (#4906) framing (4.0 stays only for the nested cascade #4905)
  • docs/media-buy/task-reference/update_media_buy.mdx:627deprecated and removed in 4.0deprecated 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.required is media_buy_id, packages and UpdateMediaBuySuccess.required is media_buy_id — both unchanged; both new fields optional siblings.
  • Enum identity: both media_buy_status and legacy status $ref the same enums/media-buy-status.json — no divergence possible at the schema level.
  • Storyboard discipline: field_value_or_absent is defined at static/compliance/source/universal/storyboard-schema.yaml:1175-1186 and already used across nine scenarios. Equality-when-present is the right gate for a deprecation window.
  • oneOf discriminator: not.anyOf arms at create-media-buy-response.json:138-164 and update-media-buy-response.json:127-152 key on the status const submitted. Adding media_buy_status does not perturb the discriminator. scripts/audit-oneof.mjs --check should still pass.
  • Changeset type: minor is 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.yaml does not equality-gate the legacy field. Three field_present path: "media_buy_status" assertions but no companion field_value_or_absent path: "status". pending_creatives_to_start.yaml has the equality gate; state-machine.yaml does not. The state-machine storyboard uses field_present not field_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 + legacy status on 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)

  1. docs/reference/migration/media-buy-status.mdx:204-210 "Before (3.0)" JSON example has two literal status keys 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 first comment inside the fence would inoculate a copy-paste reader.
  2. 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.

bokelley added a commit that referenced this pull request May 21, 2026
…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>
@bokelley bokelley dismissed aao-release-bot[bot]’s stale review May 21, 2026 17:22

Drift fixed in cd27f3c: schema descriptions on create/update success responses now say 'removed in 3.2 (#4906)' matching the migration page, changeset, whats-new bullet, and task-reference tables. Bot's catch was correct — thanks.

…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>
@bokelley bokelley force-pushed the bokelley/spec-4895-media-buy-status-rename branch from cd27f3c to 5e9cb45 Compare May 21, 2026 17:24
Copy link
Copy Markdown
Contributor

@aao-release-bot aao-release-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.json and update-media-buy-response.json: media_buy_status is a new optional field, legacy status keeps the same $ref to media-buy-status.json with deprecated: true and an updated description. Neither field is in required[]. The CreateMediaBuySubmitted arm and the not.anyOf error-branch guard pin status: { const: "submitted" } as the TaskStatus discriminator and are untouched.
  • Storyboard enforcement: pending_creatives_to_start.yaml:127-134 and :214-221 pair field_value path: "media_buy_status" with field_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-296 flips three field_present assertions to media_buy_status (pause / resume / cancel arms).
  • Enum-overlap rationale: TaskStatus and MediaBuyStatus collide on completed | canceled | rejected and diverge elsewhere. A flat-serialized MediaBuyStatus: '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 on get-media-buys-response, get-media-buy-delivery-response, and core/media-buy.json — held to the major is the right call.
  • Changeset categorization: minor is correct for a strictly additive change with deprecated: true advisory marker.

ad-tech-protocol-expert: sound. code-reviewer: sound-with-caveats — same drift items below.

Follow-ups (non-blocking — file as issues)

  1. Changeset prose contradicts itself on the removal version. .changeset/4895-media-buy-status-additive-deprecate.md:9 says "(additive-deprecate, 3.1 minor → 4.0 removal)" and :11 says 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.

  2. Cancellation-example prose at docs/media-buy/task-reference/update_media_buy.mdx:627 says "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.

  3. Migration page cross-link 404. docs/reference/migration/media-buy-status.mdx:92 links to /docs/building/implementation/task-lifecycle. Actual path is /docs/building/by-layer/L3/task-lifecycle. Mintlify will 404 it.

  4. field_value_or_absent + allowed_values is spec-supported but novel. pending_creatives_to_start.yaml:218-221 is the first use of allowed_values paired with field_value_or_absent in the corpus — every other site uses scalar value:. Spec at static/compliance/source/universal/storyboard-schema.yaml:1180-1181 explicitly 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)

  1. "Before (3.0)" JSON block has duplicate keys by design. docs/reference/migration/media-buy-status.mdx:28-35 shows 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.

  2. 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.

Copy link
Copy Markdown
Contributor

@aao-release-bot aao-release-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 CreateMediaBuySuccess and UpdateMediaBuySuccess. Neither status nor media_buy_status is in required[] (create-media-buy-response.json:102-105, update-media-buy-response.json:92-94). 3.0 sellers emitting status: "active" keep validating — deprecated: true is advisory under draft-07, not gating.
  • oneOf disjointness between CreateMediaBuySuccess / CreateMediaBuyError / CreateMediaBuySubmitted is intact. Submitted's status: { const: "submitted" } plus its not.anyOf exclusions on media_buy_id / packages still cleanly separate it from Success, and media-buy-status.json doesn't contain "submitted" so the legacy status slot can never collide with the discriminator.
  • field_value_or_absent is a real storyboard check (defined in static/compliance/source/universal/storyboard-schema.yaml), with semantics matching the MUST-equality intent. The new assertions at scenarios/pending_creatives_to_start.yaml:131-134, 218-221 do enforce the deprecation-window equality clause.
  • Changeset type minor is correct. Strictly additive, no rename, no required[] 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_buy story is consistent: there is no dedicated cancel tool — cancellation rides on update_media_buy — so UpdateMediaBuySuccess inheriting 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)

  1. Storyboard MUST-equality coverage is asymmetric. scenarios/pending_creatives_to_start.yaml pairs every field_value path: media_buy_status with a field_value_or_absent path: status companion assertion enforcing the MUST-equality clause from the schema description. state-machine.yaml:225/261/298 does not — those three sites only flipped to field_present path: media_buy_status and don't carry the companion field_value_or_absent check on legacy status. 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 a field_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.
  2. Schema description prose at create-media-buy-response.json:33 enumerates initial values as pending_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 "typically pending_creatives, pending_start, or active" or drop the enumeration.
  3. Schema top-level description at create-media-buy-response.json:5 still narrates the synchronous-success shape using "a MediaBuyStatus value (pending_creatives / pending_start / active)" without naming media_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)

  1. The "Before (3.0)" JSON example has two status keys 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 to json5 or 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.
  2. docs/media-buy/task-reference/create_media_buy.mdx Submitted-response table leaks the deprecated mental model — the parenthetical describes the sync-success branch's status as carrying a MediaBuyStatus value, which in 3.1 is the deprecated form. Rephrase to "where status carries the envelope TaskStatus and media_buy_status carries the buy's lifecycle state."
  3. Changeset .changeset/4895-media-buy-status-additive-deprecate.md was written for the original PR before #4908 was folded in; the storyboard-sweep section doesn't mention the new field_value_or_absent MUST-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.

@bokelley bokelley merged commit 06abeab into main May 21, 2026
25 checks passed
@bokelley bokelley deleted the bokelley/spec-4895-media-buy-status-rename branch May 21, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants