Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9dcf7aa
chore(3.0.x): re-cherry-pick #3461 and #3462 (envelope_field_present …
bokelley Apr 30, 2026
22a69cf
Version Packages (#3615)
aao-release-bot[bot] Apr 30, 2026
a83a2aa
docs(creative-channels): fix url_type tracker → tracker_pixel (#2986)…
bokelley Apr 30, 2026
dabd223
feat(storyboard): provides_state_for cascade-rescue field (closes #37…
bokelley May 1, 2026
8dc3d15
Version Packages (#3696)
aao-release-bot[bot] May 1, 2026
1aec78c
ci(release): auto-upload protocol tarball to GitHub Release (#3786) (…
bokelley May 1, 2026
78b1dc4
chore(3.0.x): cherry-pick + adapt 7 spec fixes from main (#3784) (#3789)
bokelley May 2, 2026
59db032
ci(release): un-exclude dist/compliance + forward-merge auto-resoluti…
bokelley May 2, 2026
4a0ed6e
ci(release): forward-merge package.json --ours, not --theirs (#3807) …
bokelley May 2, 2026
89bf772
ci(release): bridge cherry-pick divergence in forward-merge (#3811) (…
bokelley May 2, 2026
4555f01
Version Packages (#3799)
aao-release-bot[bot] May 2, 2026
d794f2b
ci(release): push forward-merge branch before peter-evans runs (#3818…
bokelley May 2, 2026
d98c9e4
spec(storyboard-schema): add optional default_agent field (closes #38…
bokelley May 2, 2026
a4bd513
spec(capabilities): relax identity.additionalProperties to true (3.0.…
bokelley May 2, 2026
b0aa07d
fix(storyboard): capture rights_id from acquire_rights response (clos…
bokelley May 2, 2026
1cb57a6
Version Packages (#3898)
aao-release-bot[bot] May 2, 2026
e4af188
docs(skill): document implementation-dependent issues[] fields (3.0.x…
bokelley May 3, 2026
91b6e2c
3.0.6 cherry-picks: governance wire-placement + ctx_metadata reservat…
bokelley May 3, 2026
525c7e9
3.0.6 follow-up cherry-picks: task_completion. prefix docs + comply_t…
bokelley May 3, 2026
2cea1dc
Version Packages (#3933)
aao-release-bot[bot] May 3, 2026
28ada0b
docs(release-notes): catch 3.0.x's release-notes.mdx up from 3.0.1 to…
bokelley May 3, 2026
b841e62
ci(storyboards): fall back to @adcp/client compliance cache path on 3…
bokelley May 3, 2026
1f46a2f
fix(compliance): chain proposal_id through proposal_finalize storyboa…
bokelley May 4, 2026
866abe2
docs(creative): backport #4153 list_creatives filter-types fix to 3.0…
bokelley May 7, 2026
d22bdc1
ci(release): extend forward-merge auto-resolution for known 3.1-track…
bokelley May 7, 2026
24c09c0
docs(skill): correct issues[] field names + split spec-optional from …
bokelley May 7, 2026
b2f7a3d
fix(compliance): measurement_terms_rejected — UUID-aliased idempotenc…
bokelley May 8, 2026
7d656da
Version Packages (#4185)
aao-release-bot[bot] May 8, 2026
8f82d46
fix(compliance): UUID-aliased idempotency_keys across remaining story…
bokelley May 8, 2026
13f8f4b
Version Packages (#4233)
aao-release-bot[bot] May 8, 2026
5f3a4f4
ci(release): backport storyboard-schema.yaml forward-merge allowlist …
bokelley May 8, 2026
5d2e7be
fix(schema,skill): align HMAC framing to RFC 9421 default in reportin…
bokelley May 9, 2026
753dbe3
docs(accounts): propagate account discovery MUST to overview (#4304)
bokelley May 9, 2026
ea2025f
Version Packages (#4289)
aao-release-bot[bot] May 9, 2026
e06b623
ci(release): narrow forward-merge auto-resolve to metadata-only files…
bokelley May 9, 2026
d931796
ci(changeset-check): backport forward-merge/* skip rule to 3.0.x (#4317)
bokelley May 10, 2026
fa86695
fix(compliance): finish $generate:uuid_v4 sweep across error/gov/sign…
bokelley May 10, 2026
d48ca0e
Version Packages (#4354)
aao-release-bot[bot] May 10, 2026
e03978d
fix(compliance): collapse key_reuse_conflict into replay_same_payload…
bokelley May 11, 2026
d805fa2
Version Packages (#4366)
aao-release-bot[bot] May 11, 2026
4e9738c
spec(compliance): document force_scenario_unsupported grading rule (#…
bokelley May 11, 2026
d8d5cfa
feat(compliance): add comply_controller_mode_gate storyboard and acme…
bokelley May 11, 2026
2133f22
fix(training-agent): add static catalog aliases for sales_guaranteed …
bokelley May 11, 2026
ad553b3
fix(storyboard): correct governance_denied to expect AcquireRightsRej…
bokelley May 11, 2026
6ed6bed
fix(schema): make account.supported_billing conditional on media_buy …
bokelley May 11, 2026
cf13380
feat(schemas): tmp identity_match_request — required seller_agent_url…
ohalushchak-exadel May 13, 2026
173402d
Version Packages (#4381)
aao-release-bot[bot] May 13, 2026
b2f9ce3
docs(capabilities): add account.supported_billing to media_buy exampl…
ohalushchak-exadel May 13, 2026
29dca4a
Forward-merge 3.0.x → main
May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 21 additions & 0 deletions .changeset/docs-capabilities-account-block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"adcontextprotocol": patch
---

Docs: add the now-required `account.supported_billing` block to the four
`get_adcp_capabilities` example JSON blocks that declare `media_buy`
support.

Since #3750 (`fix(schema): make account.supported_billing conditional on
media_buy protocol`), the response schema requires `account.supported_billing`
whenever `supported_protocols` contains `media_buy`. Four illustrative
examples in the docs (`creative/sales-agent-creative-capabilities.mdx`,
`media-buy/specification.mdx`, `reference/migration/channels.mdx`,
`reference/migration/geo-targeting.mdx`) were not updated alongside the
schema and have been failing CI's schema validation step on `3.0.x` HEAD,
blocking every other patch PR against the branch.

Each example now includes `"account": { "supported_billing": ["operator",
"agent"] }`, matching the pattern already used in
`docs/building/integration/accounts-and-agents.mdx`. Documentation only —
no protocol behavior change.
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
# Changelog

## 3.0.12

### Patch Changes

- d8d5cfa: Add `comply_controller_mode_gate` universal storyboard and `acme-outdoor-live` test kit.

New storyboard exercises the live-account denial path for `comply_test_controller`:
a seller that exposes the controller must return `FORBIDDEN` when called by a
live-mode (non-sandbox) principal. Optional phase for two-deployment sellers;
required for single-endpoint sellers that implement per-account gating.
Closes #4028.

- 6ed6bed: Fix `account.supported_billing` schema: require it only when `media_buy` is in `supported_protocols`, not unconditionally for all agents. Adds root-level `allOf` if/then guard following the existing `sync-plans-request.json` pattern. Non-media-buy agent authors should note that `supported_billing` was previously enforced on any `account` block — SDKs using code generators that drop draft-07 `if/then` (openapi-typescript, zod-to-json-schema, quicktype) should add a runtime guard to require `supported_billing` when `account` is present and `media_buy` is declared.
- 4e9738c: spec(compliance): document `force_scenario_unsupported` — UNKNOWN*SCENARIO on force*\* controller steps grades not_applicable

Sellers that implement `comply_test_controller` but have not implemented a specific `force_*` scenario arm (e.g., `force_create_media_buy_arm`) correctly return `{success: false, error: UNKNOWN_SCENARIO}`. The storyboard narrative in `create_media_buy_async.yaml` already said this grades `not_applicable` — that narrative was normative English. The runner contract, however, had no machine-readable enforcement layer for force\_\* scenarios (only `fixture_seed_unsupported` for auto-injected seed phases), so conforming runners were implementing FAILED instead of not_applicable.

Patch-eligibility justification (IETF errata test, playbook lines 261-265): the storyboard's own normative narrative text already required not_applicable; any runner grading FAILED was non-conforming against that existing MUST. This change adds the machine-readable form of a rule that was already in force. A conformant 3.0.0 implementation of the surrounding behavior would already have honored the narrative — the schema text closing the gap is an errata clarification, not a new requirement.

Changes:

- `storyboard-schema.yaml`: adds `force_scenario_unsupported` alongside `fixture_seed_unsupported`, with a normative MUST: detect the tuple (comply*test_controller IS present, resolved payload scenario begins with `force*`, response {success: false, error: UNKNOWN_SCENARIO}) and grade not_applicable before evaluating declared validations. Documents detection order to prevent misgrading absent-tool cases.
- `runner-output-contract.yaml`: adds `fixture_seed_unsupported`, `force_scenario_unsupported`, and `unresolved_scenario_reference` as recognized narrower detail values under canonical reason `not_applicable`, with the encoding MUST for `force_scenario_unsupported`.

No storyboard YAML changes — `create_media_buy_async.yaml`'s narrative was already correct; this closes the machine-readable gap the runner was missing. Runner implementation fix tracked in adcp-client (sibling-repo).

Closes #4226.

- cf13380: TMP Identity Match: add required `seller_agent_url` to the request and make
`package_ids` optional.

**Why.** The buyer's identity-match service already keeps the authoritative
set of active packages it has registered per seller. Carrying that set on
every request was redundant and forced publishers to enumerate ALL active
packages on every call to avoid the set-correlation attack on Context
Match. Identifying the seller by URL lets the buyer resolve the package
set itself.

**Changes to `static/schemas/source/tmp/identity-match-request.json`.**

- New required field `seller_agent_url` (`string`, `format: uri`). The
seller agent's API endpoint URL. Compared using the AdCP URL
canonicalization rules, consistent with `seller_agent.agent_url` on
`AvailablePackage` and `agent_url` in `adagents.json`.
- `package_ids` is now optional. When omitted, the buyer evaluates against
the full active set registered for `seller_agent_url`. When provided,
the ALL-active-packages rule still applies — partial sets remain a
correlation risk.
- Top-level description updated to reflect both modes.

**Spec changes alongside the schema.**

- Reversed prior stance forbidding seller identity on `identity_match_request`. The "What This Is Not" / SellerAgentRef guidance has been narrowed to apply only to `context_match_request`.
- Added a fail-closed rule: when `seller_agent_url` matches no seller for which the buyer has registered active packages, the buyer MUST return an empty `eligible_package_ids`, not fall back to another seller's set.
- Defined precedence when both `seller_agent_url` and `package_ids` are present: buyer evaluates against the intersection of its registered active set and `package_ids`; unknown IDs are silently dropped (not error-surfaced) so the response cannot leak registry membership.
- Reframed the package-set-decorrelation invariant as **statistical independence of `package_ids` from the current placement**, with two acceptable modes: all-active and fuzzed (random sample padded with synthetic non-existent IDs that the buyer silently drops). The page-specific subset remains forbidden.
- Strengthened temporal decorrelation: random delay alone leaks the pairing through ordering. Publishers SHOULD also randomize whether Context Match or Identity Match is sent first — each opportunity SHOULD have a roughly equal probability either way.

**Privacy boundary.** `seller_agent_url` identifies the seller agent, not
the user; no leakage across the identity boundary. Routers do NOT strip
it (unlike `country`) — buyers need it to resolve the package set.

**Backwards compatibility.** Breaking for the experimental TMP schema
(`x-status: experimental`): callers MUST now send `seller_agent_url`. The
relaxation of `package_ids` is non-breaking on its own — previously valid
requests remain valid as long as they also include `seller_agent_url`.

## 3.0.11

### Patch Changes
Expand Down
163 changes: 163 additions & 0 deletions dist/compliance/3.0.12/domains/brand/index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
id: brand_baseline
version: "1.0.0"
title: "Brand baseline"
protocol: brand
category: brand_baseline
summary: "Baseline protocol storyboard — every brand agent must declare the brand protocol in capabilities and return a schema-valid brand identity."
track: brand
required_tools:
- get_brand_identity

narrative: |
Brand protocol agents are the identity layer of AdCP. Their job is to hold
brand identity data (names, logos, colors, fonts, tone) and expose it to
other agents — buyer agents, creative agents, DSPs — that need to render
on-brand creative or verify who a campaign is for.

The baseline tests the minimum contract that every brand agent honors,
regardless of what additional capabilities (rights licensing, creative
approval) it layers on top:

1. Declare `brand` in `supported_protocols` on `get_adcp_capabilities`.
2. Respond to `get_brand_identity` with a schema-valid identity manifest.
3. Reject unknown `brand_id` values with a structured error.

Rights licensing (`get_rights`, `acquire_rights`, `update_rights`,
`creative_approval`) ships experimentally in 3.0 and is covered by the
`brand-rights` specialism storyboard, not this baseline.

agent:
interaction_model: brand_agent
capabilities: []
examples:
- "Any brand agent (simple identity host or full rights platform)"
- "Brand-owned agents (Acme Outdoor)"
- "Third-party brand identity platforms"
- "Agency-hosted brand agents"

caller:
role: buyer_agent
example: "Any buyer, creative agent, or DSP needing brand identity"

prerequisites:
description: |
The test kit provides a sample brand (Nova Motors) that any brand agent
can serve identity for.
test_kit: "test-kits/nova-motors.yaml"

phases:
- id: capability_discovery
title: "Capability discovery"
narrative: |
The buyer calls `get_adcp_capabilities` to confirm the agent declares
the brand protocol before issuing any brand-identity call.

steps:
- id: get_capabilities
title: "Check agent capabilities"
narrative: |
Verify that the agent declares `brand` in `supported_protocols`.
Without this claim the buyer MUST NOT send `get_brand_identity`.
task: get_adcp_capabilities
schema_ref: "protocol/get-adcp-capabilities-request.json"
response_schema_ref: "protocol/get-adcp-capabilities-response.json"
doc_ref: "/protocol/get_adcp_capabilities"
comply_scenario: capability_discovery
stateful: false
expected: |
Return capabilities declaring `brand` in `supported_protocols`.

sample_request:
context:
correlation_id: "brand_baseline--get_capabilities"
validations:
- check: response_schema
description: "Response matches get-adcp-capabilities-response.json schema"
- check: field_present
path: "supported_protocols"
description: "Response declares supported_protocols"

- id: brand_identity_retrieval
title: "Brand identity retrieval"
narrative: |
The buyer calls `get_brand_identity` to retrieve the brand's identity
manifest. The minimum contract is a schema-valid response that echoes
the requested `brand_id` and carries at least one name.

steps:
- id: get_brand_identity
title: "Retrieve brand identity"
narrative: |
The buyer calls `get_brand_identity` with a known `brand_id`. The
response MUST match the brand-identity schema and echo the
requested `brand_id`. Rich fields (logos, colors, fonts, tone,
visual_guidelines) are optional at the baseline level — the
minimum bar is that identity resolution works and is schema-valid.
task: get_brand_identity
schema_ref: "brand/get-brand-identity-request.json"
response_schema_ref: "brand/get-brand-identity-response.json"
doc_ref: "/brand-protocol/tasks/get_brand_identity"
stateful: false
expected: |
Return a schema-valid brand identity that echoes the requested
brand_id and includes at least one name.

sample_request:
brand_id: "nova_motors"
context:
correlation_id: "brand_baseline--get_brand_identity"
context_outputs:
- path: "brand_id"
key: "brand_id"

validations:
- check: response_schema
description: "Response matches get-brand-identity-response.json schema"
- check: field_present
path: "brand_id"
description: "Response includes brand_id"
- check: field_value
path: "brand_id"
value: "nova_motors"
description: "Returned brand_id echoes the requested brand"
- check: field_present
path: "names"
description: "Response includes brand names"

- id: unknown_brand_rejection
title: "Unknown brand rejection"
narrative: |
Agents MUST reject unknown `brand_id` values with a structured
AdCP error rather than returning an empty or fabricated manifest.

steps:
- id: get_brand_identity_unknown
title: "Reject unknown brand ID"
narrative: |
The buyer calls `get_brand_identity` with a `brand_id` the agent
does not serve. The response MUST be a structured error with a
recovery classification — not a success response with empty
fields.
task: get_brand_identity
schema_ref: "brand/get-brand-identity-request.json"
response_schema_ref: "brand/get-brand-identity-response.json"
doc_ref: "/brand-protocol/tasks/get_brand_identity"
stateful: false
expected: |
Return an AdCP error response indicating the brand is not known
to this agent.

sample_request:
brand_id: "brand_that_does_not_exist_12345"
context:
correlation_id: "brand_baseline--get_brand_identity_unknown"

expect_error: true
negative_path: payload_well_formed
validations:
- check: error_code
allowed_values:
- "brand_not_found"
- "BRAND_NOT_FOUND"
- "NOT_FOUND"
description: "Error code indicates brand-not-found"
Loading
Loading