Skip to content

spec(3.1): add capability_ids[] to PackageRequest for V2-native authoring#4845

Merged
bokelley merged 2 commits into
mainfrom
bokelley/issue-4842
May 20, 2026
Merged

spec(3.1): add capability_ids[] to PackageRequest for V2-native authoring#4845
bokelley merged 2 commits into
mainfrom
bokelley/issue-4842

Conversation

@bokelley

@bokelley bokelley commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #4842.

The V2 mental model breaks at the create_media_buy boundary today: PackageRequest only carries v1 format_ids[]. A buyer reading Product.format_options[] (the V2 path) has to translate back through v1_format_ref[] just to write a create call — exactly where the buyer commits budget.

Adds optional capability_ids: string[] to PackageRequest. Resolution rules:

  • Both present. capability_ids wins; the resolving seller routes by it and MUST NOT validate format_ids for consistency (the v1-compat hint is for intermediaries on the wire path).
  • capability_ids only. Seller resolves against the product's format_options[]; uses each declaration's v1_format_ref[] when projecting to v1-wire surfaces.
  • format_ids only. Unchanged v1 behavior.
  • Neither. Default to all formats supported by the product.

Failure modes — seller MUST reject with UNSUPPORTED_FEATURE (path packages[i].capability_ids[j]) when an entry doesn't resolve, when the product is v1-only (no format_options[] at all), or when the product's format_options[] entries don't publish capability_id values. The last case sets error.details.reason to capability_ids_not_published so V2 buyers know to fall back to format_ids[].

Dual emission — V2-native buyer SDKs targeting a heterogeneous seller population SHOULD emit format_ids alongside capability_ids so v1-only sellers — which ignore the unknown field per additionalProperties: true — still receive an explicit format set rather than silently defaulting to all-formats.

Shape — array, not singular. Parity with format_ids[]. A package may legitimately activate multiple format_options[] entries even though the 90% case is [one]. Per-creative creative-manifest keeps its singular capability_id because one manifest = one declaration — the asymmetry is intentional.

Files

  • static/schemas/source/media-buy/package-request.json — new optional field
  • docs/media-buy/task-reference/create_media_buy.mdx — Package Object row, V2 example in Format Specification, Format Workflow diagram updated (no longer says formats are REQUIRED), error table row, v2-path error example
  • .changeset/package-request-capability-ids.mdminor (additive optional field on the spec)

Doc correction (incidental)

The Package Object row for format_ids was previously documented as Required: Yes. The schema's required list has always been [product_id, budget, pricing_option_id] only — format_ids was always optional with a default of "all formats supported by the product." This PR corrects the row to Required: No to match the schema and to make the new capability_ids semantics consistent (either-or-neither, never both required).

Out of scope

  • The pre-existing error table on the same page references FORMAT_INCOMPATIBLE, which isn't actually in static/schemas/source/enums/error-code.json — this PR uses UNSUPPORTED_FEATURE for the new failure modes and leaves the pre-existing row untouched. Worth filing separately.
  • Tightening product-format-declaration.json to require capability_id always is a separate design call; this PR documents the seller obligation in the new field's description instead.

Test plan

  • CI green
  • Schema validates (precommit ran clean locally; CI fix in 2nd commit escaped embedded quotes)
  • Verify Mintlify docs render the new table row + V2 example correctly

🤖 Generated with Claude Code

…ring

Closes #4842. The V2 mental model (`Product.format_options[]` → V2-tagged
`creative-manifest` with `capability_id`) broke at the `create_media_buy`
boundary because `PackageRequest` only carried v1 `format_ids[]`. Buyers
authoring against `format_options[]` had to translate back through
`v1_format_ref[]` just to write a create call.

Adds optional `capability_ids: string[]` (array, parity with the
existing `format_ids[]` shape — a package may activate multiple
`format_options[]` entries even though the 90% case is `[one]`). Closed-set
validation: sellers MUST reject with `UNSUPPORTED_FEATURE` when an entry
doesn't resolve, when the product is v1-only, or when the product's
`format_options[]` entries don't publish `capability_id` values (latter
case carries `error.details.reason: capability_ids_not_published` so
buyers can fall back to `format_ids[]`).

Dual-emission of `format_ids` alongside `capability_ids` is SHOULD for
V2-native SDKs — v1-only sellers ignore the unknown field per
`additionalProperties: true` and would otherwise silently default to
all-formats. When both are present, `capability_ids` wins and the
resolving seller doesn't validate `format_ids` for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@aao-release-bot

Copy link
Copy Markdown
Contributor

⚠️ Argus review could not complete

The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final gh pr review). A human reviewer should take this PR.

View workflow run

This is an automated message from the Argus AI review workflow.

CI schema validation failed parsing `error.details.reason: "..."` —
the inner double-quotes broke the JSON string. Use backticks around
the enum-value reference instead.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving. Right shape, internally consistent, closes the V2-authoring gap without breaking v1 callers.

Things I checked

  • Field shape at static/schemas/source/media-buy/package-request.json:26-33 — array, items.type: string, minItems: 1. Mirrors format_ids[]. Asymmetry vs singular creative-manifest.capability_id (static/schemas/source/core/creative-manifest.json:16) is principled: one manifest = one declaration, one package can activate multiple declarations.
  • Resolution precedence and dual-emission rule are unambiguous in both static/schemas/source/media-buy/package-request.json:28 and docs/media-buy/task-reference/create_media_buy.mdx:188-189. The "neither sent → all formats supported by the product" default is restated three times (schema, table row, Format Specification prose at :946) without drift.
  • UNSUPPORTED_FEATURE is real (static/schemas/source/enums/error-code.json:104). FORMAT_INCOMPATIBLE is not — PR body flags the pre-existing phantom row as out-of-scope.
  • additionalProperties: true at static/schemas/source/media-buy/package-request.json:208 confirmed — v1-only sellers silently drop the field. Back-compat claim holds.
  • Changeset present, minor is correct for a purely additive optional field.
  • format_ids Required Yes → No in the docs table at docs/media-buy/task-reference/create_media_buy.mdx:188 is fixing latent drift, not loosening a contract: the schema required array at static/schemas/source/media-buy/package-request.json:203-207 was always [product_id, budget, pricing_option_id]. The docs row was wrong.

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

  • Error-code choice for the third failure mode. When the product carries format_options[] but no entry publishes a capability_id, the PR uses UNSUPPORTED_FEATURE with error.details.reason: "capability_ids_not_published". That case is structurally FORMAT_CAPABILITY_UNRESOLVED — already in the enum at static/schemas/source/enums/error-code.json:166 for the adagents.json / placement-definition.json side. Either broaden that code's applicability to create_media_buy, or mint a PACKAGE_CAPABILITY_UNRESOLVED analog. Editorial, not breaking — but worth picking the convention before the next PR copies the sub-discriminator pattern.
  • Seller obligation is prose-only. "Sellers MUST publish capability_id on each format_options[] entry it expects buyers to select" lives only in the new field description and docs. static/schemas/source/core/product-format-declaration.json:10-12 keeps capability_id optional with the existing "only required when multiple entries share format_kind" rule. A seller can publish V2 products that are unauthorable on the V2 path and only fail at create_media_buy time. Future PR: either tighten the declaration schema or add a get_adcp_capabilities flag so buyers can discover V2-package-authorability before eating an UNSUPPORTED_FEATURE.
  • Phantom FORMAT_INCOMPATIBLE row in the error table at docs/media-buy/task-reference/create_media_buy.mdx:921 predates this PR. File the cleanup issue per the PR body.
  • Missing example for the capability_ids_not_published branch. The new V2 invalid-capability example at docs/media-buy/task-reference/create_media_buy.mdx:1057-1067 uses supported_capability_ids as a sibling of code/message/field, matching the page's v1 supported_formats convention but not exercising the schema-prescribed error.details.reason: "capability_ids_not_published" branch. Add a second example showing the details.reason shape — that's the only branch where the schema prescribes a specific details payload, and the docs should demonstrate it.

Minor nits (non-blocking)

  1. Changeset wording. Call out the incidental format_ids: Yes → No correction in .changeset/package-request-capability-ids.md. It IS a fix to latent drift, not a contract change — but SDK authors regenerating types from this release will see the change in the generated shape and the changeset doesn't currently mention it.
  2. Schema-description size. The new capability_ids description at static/schemas/source/media-buy/package-request.json:28 is ~1.5 KB of normative prose inside a JSON description. Codegen will dump that verbatim into TS / Pydantic doc comments and into the bundled-schema artifact. Consider trimming the schema description to a short summary plus a link to create_media_buy.mdx, and let the page carry the normative resolution-rule + failure-mode block. Not a correctness issue.

Two precedents land in this PR — error.details.reason as a sub-discriminator, and ~1.5 KB of normative wire rules living in a JSON description field. Worth locking down the conventions before the next three PRs copy them.

LGTM. Follow-ups noted below.

@bokelley bokelley merged commit aa58c94 into main May 20, 2026
18 checks passed
@bokelley bokelley deleted the bokelley/issue-4842 branch May 20, 2026 16:40
bokelley added a commit that referenced this pull request May 20, 2026
…RE (#4860)

The `create_media_buy` docs referenced `FORMAT_INCOMPATIBLE` in the error
table and two JSON response examples, but the code was never defined in
`error-code.json`. SDKs that validate `errors[].code` against the published
enum would reject responses built from the docs literally.

Migrated all three references to `UNSUPPORTED_FEATURE` — semantics match
("a requested feature or field is not supported by this seller" = "format
not in the product's accepted set"). The error-table row was also merged
with the sibling `UNSUPPORTED_FEATURE` row added in #4845 (v2
`capability_ids[]` failure modes), so a single row now spans both v1
`format_ids[]` and v2 `capability_ids[]` mismatch cases.

Closes #4852.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 20, 2026
…ry (#4861)

#4845 added `PackageRequest.capability_ids[]` so V2-mental-model buyers
can author `create_media_buy` without translating back through
`v1_format_ref[]`. That path needs sellers to publish `capability_id`
on the `format_options[]` entries the buyer targets — but the field is
currently REQUIRED only when entries share a `format_kind`. For
single-entry products (the 90% case), it's optional, and V2 authoring
is silently unreachable.

Add a SHOULD: sellers SHOULD set `capability_id` on every entry, not
just when forced by a collision. Co-located with the buyer-side change
in the same minor so 3.1 release-notes readers see the buyer capability
and the seller obligation together.

Description-text only — no structural change. The 4.0 cutover will
tighten SHOULD → MUST (tracked in #4857).

Closes #4856.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 22, 2026
…TURE in create_media_buy (#4854)

FORMAT_INCOMPATIBLE appeared in the error table and two JSON examples in
create_media_buy.mdx but was never present in error-code.json. SDKs
validating errors[].code against the enum would treat it as unknown,
defeating structured recovery.

Fixes: remove FORMAT_INCOMPATIBLE row; fold v1-path guidance into the
existing UNSUPPORTED_FEATURE row (which already covers the v2/capability_ids
path); replace FORMAT_INCOMPATIBLE with UNSUPPORTED_FEATURE in both JSON
examples; update field path in both v1 examples to packages[0].format_ids[0]
for element-level precision, consistent with the v2 example's
packages[0].capability_ids[0].

Closes #4852. Surfaced during review of #4845.

https://claude.ai/code/session_01MVUhgB3tVFtW85wDQ1MATy

Co-authored-by: Claude <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.

PackageRequest lacks v2 capability_id path — V2 mental model breaks at create_media_buy boundary

1 participant