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
13 changes: 13 additions & 0 deletions .changeset/add-governance-adjustments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"adcontextprotocol": minor
---

Add attributed delivery reconciliation and a two-party, append-only campaign adjustment lifecycle for AdCP 3.2. Sellers report canonical delivery statements and evidence-bound adjustments; buyers submit separate observations, close operational governance periods without asserting final billing truth, and accept or dispute adjustments. Audit logs expose discrepancies, period state, conservative exposure, gross commitment, verified economic reductions, and accounting-mode-specific headroom without weakening sticky trailing-window fragmentation defense.

## Migration

This change breaks two experimental surfaces (`x-status: experimental`). Both surfaces are changed for the first time in 3.2 beta, making the beta publication itself the required 6-week-notice vehicle per `docs/reference/experimental-status.mdx`.

**`delivery` object** (`check-governance-response.json` / `get-plan-audit-logs-response.json`): The deprecated unbound delivery snapshot is superseded. Before: `additionalProperties: true`, no required fields (`deprecated: true`). After: six required fields (`statement_id`, `sequence`, `statement_digest`, `reporting_period`, `cumulative_spend`, `currency`), `additionalProperties: false`.

**`check_governance` `delivery_metrics`**: Required fields expand from 1 (`reporting_period`) to 8 (`statement_id`, `sequence`, `statement_digest`, `reporting_period`, `cumulative_spend`, `currency`, `seller_reference`, `canonical_payload`). Before/after request examples are in `docs/governance/campaign/tasks/check_governance.mdx`.
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@
"docs/governance/campaign/tasks/index",
"docs/governance/campaign/tasks/sync_plans",
"docs/governance/campaign/tasks/report_plan_outcome",
"docs/governance/campaign/tasks/report_plan_adjustment",
"docs/governance/campaign/tasks/check_governance",
"docs/governance/campaign/tasks/get_plan_audit_logs"
]
Expand Down
4 changes: 3 additions & 1 deletion docs/contributing/x-entity-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,14 @@ High-level groupings (see the registry for full descriptions). *Categories below
| Creative | `creative`, `creative_format` |
| Data & targeting | `audience`, `audience_evidence`, `audience_evidence_snapshot`, `signal`, `signal_activation_id`, `demographic_interval_id`, `event_source` |
| Lists & catalogs | `collection_list`, `property_list`, `catalog`, `property` |
| Plans & governance | `media_plan`, `governance_plan`, `governance_registry_policy`, `governance_inline_policy`, `governance_check`, `content_standards`, `task`, `attestation_credential` |
| Plans & governance | `media_plan`, `governance_plan`, `governance_registry_policy`, `governance_inline_policy`, `governance_check`, `governance_delivery_statement`, `governance_delivery_observation`, `governance_outcome`, `governance_adjustment`, `governance_adjustment_evidence`, `seller_adjustment`, `content_standards`, `task`, `attestation_credential` |
| Vendor services | `vendor_pricing_option`, `vendor_metric` |
| SI | `si_session`, `offering` |

**Plan vs. policy vs. check:** `governance_plan` identifies the plan container (answers *"which plan?"*); `governance_registry_policy` / `governance_inline_policy` identify a rule inside or referenced by a plan (*"which rule?"*); `governance_check` identifies a specific evaluation of a plan against its policies (*"which check?"* — round-trips between `check_governance` and `report_plan_outcome`). Pick by the question the captured value answers.

**Outcome vs. adjustment:** `governance_outcome` identifies the governance agent's settled commitment record; `governance_adjustment` identifies the governance agent's append-only adjustment record; `seller_adjustment` identifies the seller's source record and is scoped to that authenticated seller.

**Registry vs. inline policy:** Use `governance_registry_policy` when the field holds a globally-unique registry id (e.g., `uk_hfss`, `us_coppa`, `garm:brand_safety:violence`). Use `governance_inline_policy` when the field holds a plan-scoped bespoke id authored via `policy-entry.json`. Every `$ref` to `policy-entry.json` in an AdCP task schema is inline by definition — registry entries are served by a separate out-of-band API. If the field can legitimately hold either at runtime (the two ambiguous sites: `check-governance-response::findings[].policy_id`, `get-plan-audit-logs-response` audit entries, plus reserved `creative/creative-feature-result.json::policy_id` and `core/feature-requirement.json::policy_id`), leave it un-annotated and add a `$comment` starting with `"x-entity deliberately omitted"` — the gap lister recognises that phrase and skips the leaf.

The registry file is the source of truth. To see every annotated field across the repo: `git grep -l x-entity static/schemas/source`.
Expand Down
4 changes: 3 additions & 1 deletion docs/governance/campaign/responsibilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The seller-side check sends:
| `governance_context` | Yes | The signed, opaque context from the buyer-side approval. It is the authoritative plan binding. |
| `planned_delivery` | Yes | The seller's actual planned execution: budget, dates, channels, geography, placements, pacing, and other delivery parameters. |
| `phase` | Yes for lifecycle clarity | Indicates whether this is a purchase, modification, or delivery check. |
| `delivery_metrics` | Delivery phase | Required when `phase` is `delivery`; carries actual delivery performance data for pacing, spend, geography, channel, and audience-drift checks. |
| `delivery_metrics` | Delivery phase | Required when `phase` is `delivery`; carries the seller-attributed canonical statement used for pacing, spend, geography, channel, and audience-drift checks. |

The seller must not treat the buyer's intent check as sufficient by itself. The seller checks what it will actually deliver, which may differ from the buyer's request because of inventory availability, seller defaults, or implementation constraints.

Expand All @@ -62,6 +62,8 @@ The execution check is binary. If the governance agent denies it, the seller mus

After a seller responds, the buyer-side orchestrator calls [`report_plan_outcome`](/docs/governance/campaign/tasks/report_plan_outcome). This lets the governance agent reconcile the approved action with the seller's actual response and update budget state from confirmed outcomes.

For periodic delivery, the buyer names the exact seller delivery check and submits either the seller statement copy it received or an independently measured buyer observation. Governance keeps both sources attributed. A discrepancy is explicit and blocks adjustment acceptance; it is not resolved by last-writer-wins.

Outcome reporting is what prevents the governance agent from counting attempted actions as committed spend. The governance agent tracks the state that actually happened.

## Common mistakes
Expand Down
52 changes: 49 additions & 3 deletions docs/governance/campaign/specification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,50 @@ Every `check_governance` invocation MUST produce an audit log entry — retrieva

The buyer-side intent check and any seller-side execution check each produce a distinct `check_id`, while the opaque action binding in their signed `sub` remains stable. For completed or failed outcomes, `report_plan_outcome` MUST carry an exact `check_id`, `plan_id`, and `governance_context` tuple from an approved decision in that action lifecycle. The authenticated reporter MUST be the buyer-side caller from the original intent, and `purchase_type` MUST match that intent. The governance agent rejects mismatched tuples and permits only one terminal settlement per opaque action binding, regardless of whether a report names the intent or execution check (apart from an identical idempotent cached replay). Auditors reconcile that intent entry with any execution entry and the service's persisted token record. The report's monetary fields are reconciliation evidence: the governance agent derives ledger authority from its own intent record, narrowed by a matching purchase execution record when available.

#### Evidence authority and reconciliation

Campaign Governance is a three-party evidence system, not a rule that one participant is authoritative for every fact. The governance agent is selected by and works for the buyer, so its ledger is authoritative for authorization state but is not independent evidence that physical delivery or a refund occurred.

| Fact | Authoritative protocol record | What the other parties contribute |
|---|---|---|
| Spend authority and plan headroom | Governance agent's approved checks and derived ledger | Buyer proposes an action; seller may narrow the execution commitment |
| What the seller claims it delivered or billed | Seller-authenticated canonical delivery statement | Buyer may forward the copy it received or submit its own measurement |
| What the buyer observed | Buyer-authenticated delivery observation | It remains separately attributed and never overwrites the seller statement |
| Commercial release, refund, credit, or makegood | Seller report plus plan-owner review | Governance verifies identities, evidence integrity, bounds, and accounting policy |
| Current ledger and conservative exposure | Governance-derived state over all retained records | Neither a buyer nor seller can directly set this value |

For each `phase: "delivery"` check, the seller MUST provide an immutable `delivery_metrics.statement_id`, a monotonically increasing `sequence`, and a `statement_digest`. The digest is lowercase hex SHA-256 prefixed with `sha256:` over RFC 8785 JCS of:

```json
{
"seller_reference": "<planned_delivery.media_buy_id>",
"delivery_metrics": "<the complete delivery_metrics object except statement_digest>"
}
```

The seller's authenticated call attributes that statement to the seller. A statement ID is unique in that seller's namespace and binds to one governed action; reusing it for another action or different content is a conflict. A non-increasing sequence on the same action is also a conflict. Delivery evidence describes an already-authorized commitment and MUST NOT reserve that commitment a second time.

The buyer reports its side through `report_plan_outcome` with `outcome: "delivery"`, naming the exact seller delivery `check_id` and `governance_context`. `source: "seller_statement_copy"` means only that the buyer received the same seller statement; it is not independent corroboration. `source: "buyer_measurement"` is separately attributed buyer evidence. In either case, governance retains both records and compares period, cumulative spend, currency, and—when forwarded—the seller statement ID and digest.

If the records disagree, governance MUST:

- mark the governed action `disputed` and preserve both values with their source identities;
- use `max(seller_reported_spend, buyer_observed_spend)` as `conservative_exposure` while the dispute is unresolved;
- neither average the values nor choose the last writer; and
- reject adjustment acceptance while the disputed governance period remains open.

Reconciliation is append-only. The buyer submits a newer observation against the applicable canonical seller statement after the parties resolve the discrepancy. The governance agent uses the latest observation as the current reconciliation state while retaining every earlier disputed record in the audit trail.

A seller may also issue a corrected statement for the same open period by submitting a new `check_governance` delivery call with a higher `sequence`. The latest in-sequence statement becomes the applicable canonical statement; the buyer's next observation SHOULD bind to it. Earlier statements remain in the audit trail.

Campaign Governance is an operational control, not the source of truth for final billing. The authenticated plan owner MAY end an operational reporting window by submitting a new observation for that exact seller statement and period with `period_closed: true`. If the values still disagree, governance records `closed_unresolved`, freezes both parties' evidence for that period, and stops the historical discrepancy from blocking later governance or a buyer-reviewed adjustment. Neither party may add a different delivery statement or observation for the closed period. Closure does not assert a final payable amount, settle an invoice, or resolve the commercial dispute; those remain outside Campaign Governance. A seller statement for a different subsequent reporting period starts that period as `open` and `unmatched`.

Thus, a seller reporting 100 to governance and 200 to the buyer produces an explicit discrepancy, not an authoritative 100 or 200 overwrite. The converse behaves the same way. Governance's audit view exposes the current `delivery_reporting_period`, `seller_reported_spend`, `buyer_observed_spend`, `conservative_exposure`, `delivery_reconciliation_status`, and `delivery_period_state` while retaining every historical entry.

Commercial adjustments follow the same attributed-evidence rule. The authenticated seller first calls `report_plan_adjustment` with `action: "report"` and an integrity-bound evidence record. This creates a `reported` record only. The authenticated plan owner then calls the same task with `action: "review"` to accept or dispute the exact `adjustment_id`. A report becomes `verified` only after acceptance; a seller cannot unilaterally reduce net cost or restore plan headroom.

Verified decommitments require a canonical seller delivery statement and, in aggregate, cannot exceed the undelivered obligation. Seller-issued adjustment and evidence IDs are immutable within the seller's namespace and cannot be rebound to different content. Verified refunds and credits reduce economic `net_cost`; they restore reusable headroom only when the plan explicitly uses `budget.accounting_mode: "verified_net_cost"`. The default `gross_commitment` mode restores headroom only for verified decommitments. Makegoods remain non-cash audit records in both modes. Regardless of mode, the original commitment remains in trailing anti-fragmentation aggregates, preventing repeated commit-adjust-commit cycles from evading cumulative thresholds.

#### Interaction with idempotency

- **Replay of an already-completed identical request within the seller's idempotency window:** return the cached outcome without re-executing side effects or re-authorizing. Token expiry after the original commit does not invalidate the historical cached success. This ordering is essential because the idempotency window is longer than the intent-token lifetime.
Expand Down Expand Up @@ -319,6 +363,8 @@ then evaluates `aggregate` against each applicable threshold. The current incomi

**Commitments are sticky within the window.** `c.amount` is the amount committed at approval time, not the delivered amount. Under-delivery, cancellation, makegoods, and post-approval budget reductions MUST NOT decrement a commit's contribution to the aggregate before the trailing window rolls it off. Otherwise a buyer could free fragmentation headroom by cancelling an approved commit and immediately re-committing sub-threshold — full spend moves across the round trip, each leg passes in isolation. An `update_media_buy` that *increases* committed budget enters as a delta (new committed budget − prior committed); a decrease does not decrement.

[`report_plan_adjustment`](./tasks/report_plan_adjustment) separates current plan obligation from that sticky history. A verified `decommitment` restores current plan headroom, but the original outcome amount remains in the trailing-window aggregate until it rolls off. Verified refunds and credits reduce economic net cost and restore headroom only under an explicit `verified_net_cost` accounting mode; makegoods never restore cash headroom. Governance agents MUST retain gross commitment, reported and verified adjustments, economic net cost, restored headroom, and current ledger commitment as separately auditable values.

When an individual commit would be below a threshold in isolation but pushes the trailing-window aggregate above the threshold, the governance agent MUST apply the threshold's consequence (human review escalation, denial, or conditions) to that commit. Governance agents MAY expose an `aggregate_committed` field on `get_plan_audit_logs` responses so auditors can reconstruct the fragmentation-defense decision without re-deriving it from the full outcome stream. The field's shape (units, currency, window-boundary reporting) is governance-agent-specific in 3.x and will be standardized in a later revision; implementers exposing it SHOULD document their shape alongside their `get_plan_audit_logs` response.

Governance agents MAY evaluate narrower aggregation scopes additionally (per-brand, per-campaign) but MUST NOT evaluate any scope *broader* than the declared window without operator sign-off. "Broader" covers **both** dimensions: a longer trailing window (time) and a wider key tuple (e.g., collapsing across `account_id` so two accounts share an aggregate). A silently-widened scope in either dimension is as surprising to the operator as a silently-narrowed one.
Expand Down Expand Up @@ -1109,11 +1155,11 @@ Governance checks complement the buyer-side governance loop, they do not replace
| **Who checks** | Buyer's governance agent, called by orchestrator | Buyer's governance agent, called by seller |
| **When** | Before the buyer sends the request | Before confirm, on update, during delivery |
| **What's validated** | The buyer's intended action | The seller's planned and actual delivery |
| **Trust model** | Self-attested | Independently verified |
| **Trust model** | Buyer-attributed intent | Seller-attributed statement, reconciled against buyer evidence |
| **Budget tracking** | Yes (plan state) | Governance agent maintains state |
| **Ongoing monitoring** | Via `report_plan_outcome` | Via `delivery` phase |
| **Ongoing monitoring** | Buyer observations via `report_plan_outcome` | Seller statements via `delivery` phase |

The `delivery` phase gives the governance agent real-time visibility into what sellers are actually delivering. The buyer-side `report_plan_outcome` depends on the orchestrator reporting honestly; the `delivery` phase gets reports directly from the seller.
The `delivery` phase gives the governance agent a seller-authenticated claim about delivery. Buyer observations arrive separately through `report_plan_outcome`. Neither claim silently replaces the other; the [evidence reconciliation rules](#evidence-authority-and-reconciliation) define how conflicts are retained and handled.

The buyer-side and seller-side governance checks hit the same agent — the one registered on the account via `sync_governance`. The orchestrator calls it for intent checks and the seller calls it for execution checks; both conversations reach the same authority with the same plan state.

Expand Down
Loading
Loading