Skip to content

feat(media-buy): refine[] prefixed ids + optional action (adcp#2775) - #2799

Merged
bokelley merged 5 commits into
mainfrom
bokelley/prs-2774-2777
Apr 22, 2026
Merged

feat(media-buy): refine[] prefixed ids + optional action (adcp#2775)#2799
bokelley merged 5 commits into
mainfrom
bokelley/prs-2774-2777

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Closes #2775. Rearchitects the refine[] array on get_products to align with AdCP's existing naming conventions and match the shape that storyboard authors were already writing.

  • Prefixed ids: idproduct_id inside scope: "product", idproposal_id inside scope: "proposal". Every other entity id in AdCP is prefixed (media_buy_id, plan_id, creative_id, account_id…) — this was the only island.
  • Optional action with default: "include" on product and proposal scopes. Authors writing { scope: "product", product_id: "x", ask: "…" } no longer need the "action": "include" ceremony; the verb appears only when the behavior is non-default (omit / more_like_this / finalize).
  • Symmetric response: refinement_applied[] is now a discriminated oneOf echoing the same prefixed ids, with scope + the matching id field required per branch so cross-validation is a contract, not a convention.
  • Codegen friendliness: added an OpenAPI-style discriminator: { propertyName: "scope" } annotation on both request refine[] and response refinement_applied[] for clean TypeScript / Python / Pydantic narrowed unions.

Pre-GA (current version 3.0.0-rc.3), so breaking shape changes are cheap. Documented in the 3.0 release notes with a before/after migration table.

Before / after

// before
{ "scope": "product",  "id": "p1",  "action": "include", "ask": "add 16:9" }
{ "scope": "proposal", "id": "pr1", "action": "finalize" }

// after
{ "scope": "product",  "product_id":  "p1",  "ask": "add 16:9" }
{ "scope": "proposal", "proposal_id": "pr1", "action": "finalize" }

Touched

  • Schemas: get-products-request.json, get-products-response.json
  • Docs: refinement.mdx, get_products.mdx, specification.mdx, intro.mdx, media-products.mdx, whats-new-in-v3.mdx
  • Server: training-agent/task-handlers.ts + unit test for the action-default behavior
  • Allowlist: dropped 4 stale storyboard-drift entries now that storyboards match the canonical shape
  • Changeset: refine-prefixed-ids-optional-action.md (major bump, with migration table)

Test plan

  • npm run build:schemas clean
  • npm run test:schemas
  • npm run test:examples
  • npm run test:composed
  • npm run test:json-schema
  • npm run typecheck
  • Storyboard schema-ref lint ✓ (37 grandfathered, 0 new drift)
  • build:compliance ✓ (all 7 storyboard lints pass)
  • Refine-mode unit tests ✓ (3/3, including new default-action test)
  • Reviewed by ad-tech-protocol-expert, adtech-product-expert, code-reviewer subagents — all must-fix items addressed

Related but out of scope

🤖 Generated with Claude Code

bokelley and others added 5 commits April 21, 2026 22:18
Rename generic `id` → `product_id` / `proposal_id` inside each refine[]
scope branch to match AdCP's entity-id convention, and make `action`
optional with default "include" so only the non-default verbs (omit,
more_like_this, finalize) need to be spelled. Response
refinement_applied[] is now a discriminated oneOf echoing the same
prefixed ids, with scope + matching id required per branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename stale `id:` → `proposal_id:` in 8 existing proposal-finalize
  unit tests that my earlier sweep missed (code-reviewer finding —
  without this, proposal finalize tests pass for the wrong reason:
  `op.proposal_id` was undefined, so `previousProposals.find` missed).
- Add default-action regression test on proposal scope + assert
  `refinement_applied.proposal_id` echo.
- `get_products.mdx`: collapse the 85-line duplicated refinement
  walkthrough (which still held the old `id` shape) down to a pointer
  + minimal example (docs-expert finding — the duplicated block was
  the most-linked-to section from the rest of the docs).
- Trim redundant seller-side MUST from `specification.mdx` in favor of
  the orchestrator-side "MAY omit" note.
- Restore "v3 rc.1" header consistency in `whats-new-in-v3.mdx`.
- Changeset: add a note that `additionalProperties: false` now rejects
  leftover `id` — dx-expert observation to guide migration.
- `refinement.mdx`: add SHOULD guidance on cross-checking
  `refinement_applied` by echoed id rather than positional order alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	tests/storyboard-sample-request-schema-allowlist.json
Follow-up polish on PR #2799 (adcp#2775):

- Add schema-level regression tests for the pre-rename shape (send
  `{scope: "product", id: "x"}` against the new schema; assert it's
  rejected AND the error text flags the missing `product_id` /
  additional `id` so migrators can self-diagnose). Adds a third
  happy-path test proving the minimal `{scope, product_id}` shape
  (action-omitted) validates.
- `refinement.mdx`: add a dedicated "Troubleshooting: must NOT have
  additional properties" callout + a "Seller migration" section —
  the shape change is breaking for sellers too (must echo scope +
  product_id/proposal_id; must treat missing action as include).
- `whats-new-in-v3.mdx`: equal seller-side migration billing in the
  Typed refinement section — previously framed mostly from the buyer
  angle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	tests/storyboard-sample-request-schema-allowlist.json
@bokelley
bokelley merged commit 5e40c85 into main Apr 22, 2026
17 checks passed
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.

Schema: refine[] field naming — schema wants generic id+action, storyboards author proposal_id/product_id

1 participant