Skip to content

feat(media-buy): allowed_actions + available_actions for update_media_buy (#4480)#4514

Merged
nastassiafulconis merged 5 commits into
mainfrom
nastassiafulconis/allowed-actions-rfc-4480
May 15, 2026
Merged

feat(media-buy): allowed_actions + available_actions for update_media_buy (#4480)#4514
nastassiafulconis merged 5 commits into
mainfrom
nastassiafulconis/allowed-actions-rfc-4480

Conversation

@nastassiafulconis
Copy link
Copy Markdown
Collaborator

@nastassiafulconis nastassiafulconis commented May 14, 2026

What this implements

Spec changes for #4480allowed_actions on products, available_actions on buys, structured ACTION_NOT_ALLOWED rejection. Composes with #4425's requires predicate grammar.

This is PR #1 of the RFC rollout — schema + docs only. SDK and consumer work follows. Wire-compat: additive on the response side; existing valid_actions[] callers keep working through 3.x.

In scope (this PR)

  • closed media-buy-valid-action enum extended with finer-grained values; legacy coarse values marked via x-deprecated-enum-values (removed in 4.0)
  • enumMetadata.<action>.update_fields per action (dotted paths into update_media_buy body) so SDKs and codegen dispatch from schema metadata; enumMetadata.<legacy>.rollup lets SDKs hide legacy values when finer rollup targets are present
  • allowed_actions[] on Product (advisory template: modes[], optional allowed_statuses[], optional sla, optional terms_ref)
  • available_actions[] on get_media_buys / create_media_buy / update_media_buy responses (authoritative per-buy: singular mode, optional sla, optional terms_ref; uniquely keyed by action)
  • media-buy-action-mode enum: self_serve | conditional_self_serve | requires_proposal | requires_approval
  • ACTION_NOT_ALLOWED error code + typed error-details/action-not-allowed.json (attempted_action, reason, currently_available_actions)
  • normative action → field mapping table in update_media_buy.mdx
  • valid_actions[] deprecated in favor of available_actions[] (removed in 4.0); sellers SHOULD populate both during 3.x

Follow-up PRs I'll drive

  • @adcp/client SDK consumer: pre-flight helpers (canExtendFlight(buy), canRemoveCreative(buy), etc.) driven by available_actions[] and enumMetadata.update_fields; structured handling of ACTION_NOT_ALLOWED. Validates the schema surface end-to-end.
  • Server-side dispatch enforcement (ask Rename Audience Activation Protocol to Signals Activation Protocol #6, source): coordinating with adcp-go maintainers as a fast follow. The typed enumMetadata.update_fields makes the server-side check near-mechanical (action → covered fields → reject mismatches before reaching the handler), so this should be a small PR per server SDK rather than a full re-implementation.
  • Broader creative action split (ask Add foundational publisher formats and extension mechanism #4, source): swap_pre_approved_creative vs add_new_creative. Partial coverage in this PR (replace_creative, update_creative_assignments, remove_creative). Holding the rest until a real consumer workflow distinguishes them from replace_creative — the enum is easier to extend than to retract, and shipping speculative splits risks two values that overlap in practice.
  • Drift telemetry: last_verified timestamp on allowed_actions[] entries and/or honored-rate expectations, to prevent defensive over-declaration the way supports_proposals has rotted. Likely lands alongside constraint metadata once the shape is clearer.
  • Storefront-level templates with per-product override (ask Improve documentation framework and remove premature certification references #3, source) — its own RFC; happy to draft if useful.

Depends on other RFCs

Reviews

Schema / protocol / DX angles all covered before WG submission. Audit fixes folded in: frequency_cap singular, package-level flight-date paths added, typed error-details schema, x-deprecated-enum-values to match repo convention, MUST-prefer guidance in the docs body.

Test plan

refs #4480, #4425

nastassiafulconis and others added 4 commits May 13, 2026 20:21
…ys (#4480)

structured action vocabulary for update_media_buy capability discovery so
buyers can pre-flight which mutations are valid instead of failing mid-flight.

- extends media-buy-valid-action enum with finer-grained values; legacy coarse
  values retained for 3.x backwards compat (removed in 4.0)
- adds allowed_actions[] on Product (advisory template, modes[], allowed_statuses[])
- adds available_actions[] on get_media_buys, create_media_buy, update_media_buy
  responses (authoritative per-buy, singular mode, optional sla, optional terms_ref)
- adds enums: media-buy-action-mode, action-not-allowed-reason
- adds core types: sla-window, product-allowed-action, media-buy-available-action
- adds ACTION_NOT_ALLOWED error code + typed error-details/action-not-allowed.json
- adds enumMetadata on media-buy-valid-action with update_fields per action plus
  deprecated+rollup on legacy coarse values so SDKs can dispatch and hide legacy
  values when finer rollup targets are present
- deprecates valid_actions[] in favor of available_actions[] (removed in 4.0)
- documents normative action -> field mapping in update_media_buy.mdx

composes with #4425's requires predicate grammar.

refs #4480, #4425

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…r (3.1)

#4480 adds ACTION_NOT_ALLOWED on main. 3.0.x is wire-stable, so it's held
for the 3.1 minor release per the dispositions policy. Pairs with the new
allowed_actions[] / available_actions[] surface.

refs #4480
…#4480)

audit-pass cleanups from final review before WG submission:

- align legacy-value deprecation with repo convention: x-deprecated-enum-values
  at top level (matches specialism.json pattern); drop deprecated: true from
  enumMetadata, keep rollup
- restore "Media Buy Valid Action" title (was renamed inadvertently)
- add packages[].start_time / packages[].end_time to flight-date actions'
  update_fields; previously only buy-level dates were mapped, which left
  package-level date updates without a mapped action
- expand update_targeting field paths in the action -> field mapping table to
  fully qualify the keyword/negative-keyword incremental fields
- add explicit MUST-prefer guidance in the doc body so SDK authors reading
  only the docs page see the consumer rule, not just the schema descriptions

refs #4480
Copy link
Copy Markdown
Contributor

@bokelley bokelley left a comment

Choose a reason for hiding this comment

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

Solid RFC. Strong shape overall — flagging items I'd want addressed (or explicitly acknowledged) before WG submission.

What works

  • Two-tier shape (product template / buy-authoritative) is correct. Mirrors cancellation-policy and reporting capabilities; divergence between product allowed_actions[] and resolved available_actions[] is expected and called out.
  • enumMetadata.update_fields is the right move. SDK dispatch reading dotted paths from the enum entry — not parsing the prose table — kills a class of doc-drift bugs. Same pattern as recovery/suggestion on error-code.json.
  • ACTION_NOT_ALLOWED with currently_available_actions in error.details saves the round-trip, matches the typed error-details/*.json convention, and additionalProperties: true on the details object is right (extension surface).
  • Rollup metadata for legacy values is clean — SDKs hide update_budget when finer-grained values are present in the same payload. Migration path is explicit.
  • x-deprecated-enum-values + 4.0 removal target matches repo convention; AHEAD-set disposition (held-for-next-minor, 3.1) is correct for an enum extension on a wire-stable surface.
  • Scope discipline. Holding swap_pre_approved_creative until a real consumer distinguishes it from replace_creative is right — easier to extend than retract.

Items to address

  1. uniqueItems: true does not enforce action-keying. The changeset/PR text says "uniquely keyed by `action`; sellers MUST NOT emit two entries with the same `action`," but `uniqueItems` only catches structurally identical objects. `[{action:"pause",mode:"self_serve"},{action:"pause",mode:"requires_approval"}]` passes validation, and predicate evaluators indexing by action will silently pick one. Either add a conformance/contract test that catches duplicate-action arrays, or note in the schema `description` that the MUST is enforced by validators, not by the schema.

  2. Direction-of-change isn't expressible from `update_fields` alone. `increase_budget` / `decrease_budget` / `reallocate_budget` all dispatch to `packages[].budget`; `extend_flight` / `shorten_flight` / `update_flight_dates` all touch the same date fields. Server-side dispatch enforcement (described in the follow-up plan as "near-mechanical") actually needs to diff request-vs-current state to pick the right action. Worth surfacing this in the field-mapping table so the next implementer doesn't assume the schema is self-sufficient and re-derive the comparison badly.

  3. `mode_mismatch` recovery is a flow switch, not a retry. Buyer SDK can't re-call `update_media_buy` for a `requires_proposal` action — they need `create_proposal` / `finalize_proposal`. The current `suggestion` prose says "re-issue via the flow named" but I'd sharpen it: this is a flow switch, not a retry against the same task.

  4. `conditional_self_serve` ships as a forward-decl. The mode lands in 3.1 but the tolerance grammar belongs on #4425. Until #4425 ships, sellers declaring this mode have no schema-level way to communicate tolerances. Either acknowledge that explicitly in `enumDescriptions[conditional_self_serve]` ("tolerance expression composes with #4425; until that lands, tolerances are declared out-of-band") or hold the enum value until the paired grammar is ready.

  5. `terms_ref` is a free-string forward ref. Schema accepts anything until the buy-terms RFC lands. Worth a one-liner saying the schema will tighten when the buy-terms namespace ships, so reviewers don't pattern-match this as a permanently loose pointer.

  6. TOCTOU on mode resolution. `available_actions[].mode` is read at one moment; buy state can change before the mutation arrives. The `mode_mismatch` error covers it, but a sentence in `media-buy-available-action.json` description — "advisory at moment of emission; sellers MAY resolve to a different mode by mutation time" — would close the loop.

  7. `remove_creative` field path. The mapping table says "Creative removed from `packages[].creatives[]` or `creative_assignments`," but `update_media_buy` is PATCH semantics. Worth confirming the request schema actually supports a removal shape — is removal expressed by omitting the creative from a replacement array, or via an explicit delete primitive? Today's request schema doesn't obviously support either; that ambiguity would land in SDKs.

Follow-up plan

The rollout sequence (SDK consumer to validate the schema surface, server-side enforcement as a small per-server fast-follow, drift telemetry tied to the same rot pattern as `supports_proposals`) is the right shape. Drift telemetry in particular is worth landing sooner rather than later — defensive over-declaration is the predictable failure mode here.

response to PR #4514 review feedback:

- action-uniqueness invariant moved from uniqueItems (catches only
  structurally-identical objects) into the schema description on both
  product-allowed-action and media-buy-available-action, with explicit
  validator-MUST language
- direction-of-change note in update_media_buy.mdx field-mapping table:
  increase/decrease/reallocate and extend/shorten share update_fields paths;
  the action resolves from request-vs-current diff, which server-side
  enforcement MUST perform
- mode_mismatch recovery prose sharpened to "flow switch, not a retry against
  update_media_buy" in both action-not-allowed-reason.json and
  error-code.json's enumMetadata suggestion
- conditional_self_serve enumDescription explicitly notes tolerances are
  declared out-of-band until #4425's requires grammar lands; buyers cannot
  statically predict auto-approval from this surface alone today
- terms_ref description tightened: schema accepts any string for now and
  will tighten to a structured reference when the buy-terms RFC ships
- TOCTOU note on media-buy-available-action: mode/sla advisory at emission;
  state can change before the mutation arrives; seller MAY resolve to a
  different mode at mutation time and reject with ACTION_NOT_ALLOWED
- remove_creative mapping clarified: removal is "omit from replacement array"
  via PATCH semantics (no explicit delete primitive in 3.x)

refs #4480
Copy link
Copy Markdown
Contributor

@bokelley bokelley left a comment

Choose a reason for hiding this comment

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

Re-reviewed against ff81fe5 — all seven items addressed cleanly.

# Item Resolution
1 uniqueItems action-keying Validator-MUST language added to both core schemas
2 Direction-of-change dispatch Field-mapping table now states server-side enforcement MUST diff request-vs-current
3 mode_mismatch flow switch Sharpened in both enum description and recovery suggestion, with explicit create_proposal/finalize_proposal + webhook callouts
4 conditional_self_serve forward-decl Honest framing: buyers cannot statically predict auto-approval from this surface until #4425 lands
5 terms_ref forward ref Both schemas note tightening when buy-terms RFC ships
6 TOCTOU on mode resolution mode/sla called out as advisory-at-emission
7 remove_creative removal shape Clarified as replacement-semantics; no explicit delete primitive in 3.x

One render-only nit (not blocking): the new direction-of-change paragraph in update_media_buy.mdx sits mid-table between the budget rows and the targeting rows. The blank lines on either side should let the table re-open, but worth eyeballing the rendered MDX output before WG submission to confirm the table doesn't break visually. Content is right either way.

Approving — ready for WG. Looking forward to the SDK consumer PR exercising the schema surface end-to-end.

@nastassiafulconis nastassiafulconis merged commit b5d64ea into main May 15, 2026
18 checks passed
@nastassiafulconis nastassiafulconis deleted the nastassiafulconis/allowed-actions-rfc-4480 branch May 15, 2026 21:56
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.

2 participants