Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/proposal-decline-refinement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Add proposal opportunity and decline feedback to the media-buy flow. Buyers can now provide optional buyer-reported `opportunity` context across `get_products` and `create_media_buy` calls, while proposal refine entries support `action: "decline"` with a required `proposal_version` and reason taxonomy. `get_products` also supports `buying_mode: "open"` to reload unresolved products/proposals already associated with the caller/account/opportunity without adding a new task or response grouping object. Versioned proposals carry `proposal_version` through `create_media_buy` so sellers can verify the accepted offer version and reject execution of previously declined proposal versions. Opportunity close reasons distinguish `accepted_with_seller` from loss outcomes, and proposal-capable sellers get compliance coverage for decline acknowledgement, duplicate declines, and create rejection after decline.
23 changes: 21 additions & 2 deletions .github/workflows/check-migration-numbers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,27 @@ jobs:
# Union of both file lists, unique by full filename.
all_files=$(printf '%s\n%s\n' "$local_files" "$main_files" | sort -u | sed '/^$/d')

# Extract the leading number prefix and look for duplicates.
dupes=$(echo "$all_files" | sed 's/_.*//' | sort | uniq -d)
prefix_dupes() {
printf '%s\n' "$1" | sed '/^$/d' | sed 's/_.*//' | sort | uniq -d
}

# Local duplicates must always fail: they are present in the checked-out
# ref (PR merge, merge-group commit, or main). Union duplicates catch the
# stale-merge case where local and current main are individually clean but
# collide with each other. If current main already has a duplicate and this
# PR fixes it, don't let the main-only duplicate make the fixing PR
# impossible to merge.
local_dupes=$(prefix_dupes "$local_files")
main_dupes=$(prefix_dupes "$main_files")
union_dupes=$(prefix_dupes "$all_files")

dupes="$local_dupes"
for prefix in $union_dupes; do
if ! printf '%s\n' "$main_dupes" | grep -qx "$prefix"; then
dupes=$(printf '%s\n%s\n' "$dupes" "$prefix")
fi
done
dupes=$(printf '%s\n' "$dupes" | sed '/^$/d' | sort -u)

if [ -n "$dupes" ]; then
echo "::error::Duplicate migration number prefixes found: $dupes"
Expand Down
2 changes: 1 addition & 1 deletion docs/building/by-layer/L3/async-operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Any AdCP task can return one of these statuses. The server chooses based on what
| `sync_catalogs` | Large feeds or feeds requiring content policy review |
| `activate_signal` | Platform deployment pipelines |

These operations integrate with external systems or require human approval. Wholesale feed reads are the exception: `get_products buying_mode: "wholesale"` and `get_signals discovery_mode: "wholesale"` stay synchronous repair/reconciliation reads and report partial completion with `incomplete[]`, not `submitted`.
These operations integrate with external systems or require human approval. Wholesale feed reads are the exception: `get_products buying_mode: "wholesale"` and `get_signals discovery_mode: "wholesale"` stay synchronous repair/reconciliation reads and report partial completion with `incomplete[]`, not `submitted`. `get_products buying_mode: "open"` is also a synchronous state read; sellers return the unresolved products/proposals they already associate with the caller/account/opportunity rather than routing through `submitted`.

## Timeout Configuration

Expand Down
2 changes: 1 addition & 1 deletion docs/contributing/x-entity-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ High-level groupings (see the registry for full descriptions). *Categories below
|---|---|
| Brand & rights | `advertiser_brand`, `rights_holder_brand`, `rights_grant` |
| Account & party | `account`, `operator` |
| Media buy | `media_buy`, `package`, `product`, `product_pricing_option` |
| Media buy | `opportunity`, `proposal`, `media_buy`, `package`, `product`, `product_pricing_option` |
| Creative | `creative`, `creative_format` |
| Data & targeting | `audience`, `signal`, `signal_activation_id`, `event_source` |
| Lists & catalogs | `collection_list`, `property_list`, `catalog`, `property` |
Expand Down
6 changes: 5 additions & 1 deletion docs/media-buy/product-discovery/media-products.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,8 @@ A proposal is a recommended buying strategy that groups products with suggested

Key characteristics:
- **Actionable**: A returned proposal is buyable through its lifecycle. `proposal_status: "draft"` means finalize first; `proposal_status: "committed"` means execute via `create_media_buy` with `proposal_id` before `expires_at`.
- **Versioned**: Sellers SHOULD include `proposal_version` when a proposal has mutable execution terms, and MUST include it on proposals they expect buyers to resolve explicitly by accepting or declining a specific version. Buyers use that version when declining a proposal and MUST carry it into `create_media_buy` when accepting a versioned proposal.
- **Opportunity-aware**: Buyers MAY carry the same `opportunity.opportunity_id` across discovery, `open` retrieval, refinement, proposal decline, and `create_media_buy` so both sides can connect proposal outcomes to the broader planning cycle.
- **Budget-agnostic**: Allocations use percentages, allowing the same proposal to scale to any budget
- **Forecast-equipped**: Proposals and allocations can include delivery forecasts to help buyers evaluate expected performance before purchase

Expand All @@ -907,6 +909,7 @@ Key characteristics:
```json
{
"proposal_id": "swiss_balanced_v1",
"proposal_version": "1",
"name": "Swiss Multi-Channel Plan",
"description": "Balanced coverage across devices and language regions",
"allocations": [
Expand Down Expand Up @@ -1010,6 +1013,7 @@ To execute a committed proposal, provide the `proposal_id` and `total_budget` in
```json
{
"proposal_id": "swiss_balanced_v1",
"proposal_version": "1",
"total_budget": {
"amount": 50000,
"currency": "USD"
Expand All @@ -1023,7 +1027,7 @@ To execute a committed proposal, provide the `proposal_id` and `total_budget` in
The publisher converts the proposal's allocation percentages into packages:
- `ch_desktop_de`: 20% × \$50,000 = \$10,000

Finalization is the seller commitment step: it firms pricing, terms, availability, and any inventory hold. It is not buyer acceptance. `create_media_buy(proposal_id)` is the acceptance/execution step. Sellers reject attempts to execute a draft proposal with `PROPOSAL_NOT_COMMITTED`; finalize with `get_products` in `refine` mode and `action: "finalize"` before retrying create.
Finalization is the seller commitment step: it firms pricing, terms, availability, and any inventory hold. It is not buyer acceptance. `create_media_buy(proposal_id)` is the acceptance/execution step. Sellers reject attempts to execute a draft proposal with `PROPOSAL_NOT_COMMITTED`; finalize with `get_products` in `refine` mode and `action: "finalize"` before retrying create. When a versioned proposal is accepted, sellers SHOULD echo `proposal_id` and `proposal_version` on the create response so buyers can reconcile the accepted proposal with the resulting `media_buy_id`.
- `ch_desktop_fr`: 30% × \$50,000 = \$15,000
- etc.

Expand Down
Loading
Loading