Skip to content

spec(governance): rename body-level status on check_governance and report_plan_outcome (#4897)#4902

Merged
bokelley merged 1 commit into
mainfrom
bokelley/spec-4897-governance-status-rename
May 21, 2026
Merged

spec(governance): rename body-level status on check_governance and report_plan_outcome (#4897)#4902
bokelley merged 1 commit into
mainfrom
bokelley/spec-4897-governance-status-rename

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #4897.

Summary

Frees the top-level status key for envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization. The body-level governance field and the envelope share a root key but carry different enums; whichever wins on the wire, the other is silently destroyed and no validator catches it.

WG-recommended Option A per the issue triage (both ad-tech-protocol-expert and adtech-product-expert converged):

  • check-governance-response.json: statusverdict (enum unchanged: approved | denied | conditions)
  • report-plan-outcome-response.json: statusoutcome_state (enum unchanged: accepted | findings)
  • get-plan-audit-logs-response.json: entries[].statusentries[].verdict (vocabulary cascade)

Other status fields on the same response shapes (plans[].status, governed_actions[].status) are lifecycle states, not verdicts — left unchanged.

Scope

Surface Files
Schemas static/schemas/source/governance/{check-governance,report-plan-outcome,get-plan-audit-logs}-response.json
Docs (~25 example bodies + tables + prose) docs/governance/overview.mdx, docs/governance/campaign/{audit-trail,specification}.mdx, docs/governance/campaign/tasks/{check_governance,report_plan_outcome,get_plan_audit_logs}.mdx
Migration note docs/reference/whats-new-in-3-1.mdx (Final-spec clarifications batch)
Storyboards static/compliance/source/specialisms/governance-{spend-authority,delivery-monitor}/*.yaml, static/compliance/source/protocols/governance/index.yaml
Test fixture tests/composed-schema-validation.test.cjs

Storyboard sweep correction: the issue triage scoped storyboards as "no yaml renames needed." During implementation found four storyboards with field_present / field_value assertions keyed on path: "status" against check-governance-response.json — updated to path: "verdict". Also corrected one stale expected block in protocols/governance/index.yaml that referenced status: recorded (not in the schema enum) → now outcome_state: accepted.

Adopter impact

Wire-shape change on three experimental governance schemas (x-status: experimental). Buyers and sellers rename one property name per emitter / consumer; enum values are unchanged. SDK regen required for @adcp/client, adcp-go, and the Python client. Per the experimental-surface contract, this is a sanctioned 3.1 pre-GA adjustment.

Test plan

  • npm run build:schemas — clean
  • npm run build:compliance — clean (all storyboard lints that key on the verdict path now resolve)
  • npm run test:schemas — 8/8
  • npm run test:examples — 36/36
  • npm run test:composed — 43/43 (one pre-existing failure on report_plan_outcome accepts replayed fixed by updating the fixture to use outcome_state)
  • npm run test:json-schema — 270/270
  • npm run test:rejection-arm-mutex — 4/4
  • npm run test:docs-nav — 15/15
  • npm run test:storyboard-doc-parity — 0 fail
  • npm run test:storyboard-contradictions — 0 fail
  • npm run test:storyboard-validations-paths — 1 pre-existing failure unrelated to this change (brand-rights/governance_denied.yaml; verified on origin/main)
  • npm run test:storyboard-context-output-paths — 1 pre-existing failure on main (sales-guaranteed)
  • npm run test:storyboard-sample-request-schema — 1 pre-existing failure on main (native_in_feed)

Related

🤖 Generated with Claude Code

…report_plan_outcome (#4897)

Frees the top-level `status` key for envelope task-status (TaskStatus) under
MCP flat-on-the-wire serialization. The body-level governance field and the
envelope share a root key but carry different enums; whichever wins on the
wire, the other is silently destroyed and no validator catches it.

WG-recommended Option A per the issue triage (both ad-tech-protocol-expert
and adtech-product-expert):

- check-governance-response.json: status → verdict (enum unchanged:
  approved | denied | conditions). Updates the three if/then discriminator
  blocks, the required[], and prose threads on findings/conditions/expires_at.
- report-plan-outcome-response.json: status → outcome_state (enum unchanged:
  accepted | findings). Updates required[] and the findings prose thread.
- get-plan-audit-logs-response.json: entries[].status → entries[].verdict
  (cascade for vocabulary consistency with check-governance-response). Other
  status fields on the same response (plans[].status, governed_actions[].status)
  are lifecycle states, not verdicts — left unchanged.

Docs swept (~25 example bodies + table descriptions + prose):
- docs/governance/overview.mdx
- docs/governance/campaign/tasks/{check_governance,report_plan_outcome,get_plan_audit_logs}.mdx
- docs/governance/campaign/{audit-trail,specification}.mdx
- docs/reference/whats-new-in-3-1.mdx (migration note in Final-spec
  clarifications batch)

Storyboards swept — the triage scoped this as "no yaml renames needed" but
during implementation found four storyboards with field_present /
field_value assertions keyed on path: "status" against
check-governance-response. Updated to path: "verdict":
- specialisms/governance-spend-authority/{index,denied}.yaml
- specialisms/governance-delivery-monitor/index.yaml
- protocols/governance/index.yaml (plus a stale expected block referencing
  status: recorded — not in the enum — corrected to outcome_state: accepted)

Test fixture: composed-schema-validation report_plan_outcome case updated
to use outcome_state.

Adopter impact: wire-shape change on three experimental governance schemas
(x-status: experimental). Buyers and sellers rename one property name per
emitter / consumer; enum values unchanged. SDK regen required.

Related:
- #4876 — envelope status REQUIRED (beta.2)
- #4895 — companion media-buy collision (separate PR)
- #4896 — per-task envelope fold; carve-outs for these two schemas can be
  removed once this lands

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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. Clean rename, comprehensive sweep, sound rationale. Frees status for the envelope task-status while preserving the lifecycle status fields on plans[] and governed_actions[] — that disambiguation is the load-bearing part.

Things I checked

  • check-governance-response.json:11-74 — all three if/then discriminator blocks rekeyed on verdict; every if.properties.verdict paired with verdict in if.required; original conditions → require conditions+minItems:1, denied → require findings+minItems:1, approved|conditions → require expires_at semantics reproduced exactly. No stale status keys leftover.
  • check-governance-response.json:216required: [check_id, verdict, plan_id, explanation] correctly carries the renamed field.
  • report-plan-outcome-response.json:18-25,89-92outcome_state (right shape — accepted | findings is a state, not a verdict; reusing verdict would have been wrong), enum unchanged, required[] updated.
  • get-plan-audit-logs-response.json:258entries[].verdict cascade with $ref to /schemas/enums/governance-decision.json. plans[].status and governed_actions[].status correctly preserved as lifecycle states.
  • verdict choice cross-checked against existing AdCP idiom — content-standards/calibrate-content-response.json and enums/binary-verdict.json use the same vocabulary.
  • Changeset type minor against x-status: experimental on all three schemas — consistent with prior experimental-surface renames (#4196, #4796) and with the documented contract at docs/reference/experimental-status.mdx.
  • Storyboard sweep — governance-spend-authority/{index,denied}.yaml, governance-delivery-monitor/index.yaml, protocols/governance/index.yaml all consistent; the stale status: recorded block in protocols/governance/index.yaml:601 (value never in the enum) correctly corrected to outcome_state: accepted.
  • docs/reference/whats-new-in-3-1.mdx:202 covers the rename, calls out the experimental-surface justification, lists migration steps.
  • CI: Storyboards (/governance) and Server integration tests both green at current floors.

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

  • Cascade miss: entries[].outcome_status. get-plan-audit-logs-response.json:346 still defines a free-string outcome_status on outcome entries — surfaces the same concept as the renamed outcome_state on report_plan_outcome. The PR rationale explicitly invokes vocabulary consistency for the entries[].verdict cascade; the symmetric outcome-side cascade is missing. Either rename to outcome_state and constrain to the same enum, or add one sentence to the schema description naming the deliberate divergence.
  • summary.statuses is now misleading. get-plan-audit-logs-response.json:91 keeps the property name statuses but its keys are approved | denied | conditions | human_reviewed — i.e. it counts verdicts. After this PR, the rest of the file calls those "verdicts." summary.verdicts (or verdict_counts) restores consistency; the doc table at get_plan_audit_logs.mdx:229 inherits the same word-overload.
  • Training-agent server still emits the old field names. server/src/training-agent/governance-handlers.ts:1312 (handleReportPlanOutcomestatus:), :1498 (handleGetPlanAuditLogs entries → status:), :1641 (buildCheckResponsestatus:). CI is green because the runtime validates against SDK-bundled schemas, not in-repo source — but once @adcp/sdk regens off this PR, the server stops conforming. Sequence: rename in the SDK regen, then flip the three emission sites and the ~25 assertion sites in server/tests/unit/training-agent.test.ts. Worth a tracking issue so it doesn't fall through the cracks between SDK and server.
  • 6-week notice anchor. Experimental-surface contract requires notice; the changeset should cite the date or the issue (#4897) as the clock-start. Reads as wire-shape change with no notice attestation today.

Minor nits (non-blocking)

  1. Status values heading survived. docs/governance/campaign/tasks/check_governance.mdx:88 still reads ## Status values and the table column header at L90 is | Status |. The prose underneath (L95, L102) was correctly migrated to "verdict" — the heading and column header are the stragglers.
  2. Triage scope. The issue triage initially scoped storyboards as "no yaml renames needed" — four storyboards needed renames. Worth noting for future spec-rename triages; the wire-shape change always cascades into field_present / field_value assertions, even when the storyboard runner's lint suite doesn't catch them upfront.

Approving. The cascade miss on outcome_status is the one I'd want resolved before 3.1 GA — file it as a follow-up so the experimental-surface contract isn't paying down a second rename later.

@bokelley bokelley merged commit 989da51 into main May 21, 2026
25 checks passed
@bokelley bokelley deleted the bokelley/spec-4897-governance-status-rename branch May 21, 2026 14:57
bokelley added a commit 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 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 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 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 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 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: rename body-level status discriminator on check_governance and report_plan_outcome (3.1 GA)

1 participant