Skip to content

RFC: seller acceptance criteria for campaign governance-agent bindings #6758

Description

@lukasz-pubx

Successor to #6656 (closed) — the one mechanism from that RFC's triage that all three expert reviews converged on as spec-ready ("clean accepted_verifiers precedent, non-breaking") and that the follow-up split (#6749, #6750) did not carry. Filed standalone. This RFC makes no authority claim: campaign governance ("spend approval") remains the buyer's split-authority infrastructure; the ask is operational — acceptance criteria over an integration counterparty the seller is otherwise assigned without any say.

Amended 2026-08-22 per triage and review comments: declaration rehomed to adcp.governance_enforcement (the governance block is gated on governance protocol support); acceptance restructured as a typed any_of matcher union; the verification matcher fully specified (with URL-only V1 named as an acceptable resolution); account scoping resolved as advisory-capabilities / authoritative-sync_governance; rejected-binding hygiene and non-discrimination MUSTs added; the delegated-intervention recovery case added to Motivation.

Motivation

Grounding: citations from the repo at 17cb0f59776a0ecc68dd912dc43b640442002d74 (main, 2026-08-21); re-verify at implementation time.

The binding is buyer-initiated; the seller's role is to persist, call, and comply. docs/accounts/tasks/sync_governance.mdx:

"Sync the governance agent endpoint for specific accounts. The seller persists the agent and calls it via check_governance during media buy lifecycle events."

The task's Authorization section verifies the buyer's authority over the account; no surface exists for the seller to accept or decline the binding itself.

The seller is a forced integration counterparty, and sends it sensitive data. Under docs/governance/campaign/specification.mdx § Seller enforcement, a seller advertising governance enforcement MUST verify tokens against the bound agent and, for online_execution_check tasks, MUST call check_governance with its computed planned_delivery — its delivery parameters for the buy — and MUST NOT proceed on denial. Availability, latency, and data handling of the bound agent sit directly on the seller's execution path.

The bound agent controls recovery, not just checks. resumes_execution is a governed trigger (static/schemas/source/media-buy/control-media-buy-request.json, x-governed-commitment), and the unreachable-gate default is halt — docs/governance/campaign/specification.mdx:

"If the governance agent is unreachable (timeout, network error), the seller MUST NOT proceed with the media buy. … reject the media buy with a GOVERNANCE_UNAVAILABLE error if the agent remains unreachable."

The safe-by-design consequence: pausing is always possible, but un-pausing a media buy requires the bound agent to be reachable and responsive. An unavailable or slow agent leaves paused inventory stranded on the seller's books — in delegated or emergency-intervention scenarios, at exactly the worst moment — and the seller had no say in the counterparty whose availability determines when its inventory can resume delivering.

The spec already treats agent data-handling as a trust decision — for the buyer only. The same specification section:

"Operators who treat shopping intent as commercially sensitive SHOULD choose governance agents whose data-handling posture they trust."

The seller sends planned_delivery to the same agent and gets no equivalent choice. The trust advice exists; only one of the two parties transmitting sensitive data to the agent can act on it.

The precedent is exact. Creative governance already solves this shape: static/schemas/source/core/creative-policy.json (accepted_verifiers — "The seller is the verifier-of-record: it is the seller, not the buyer, that decides which agent it will call"), with the typed rejection PROVENANCE_VERIFIER_NOT_ACCEPTED in static/schemas/source/enums/error-code.json, and URL matching governed by docs/reference/url-canonicalization.mdx. There, the seller calls an agent chosen by the ecosystem and declares which it accepts; here, the seller calls an agent chosen by the buyer and declares nothing.

Problem

  1. A buyer binds a governance agent to the account via sync_governance. The agent may be operated by anyone — including the buyer itself or a vendor whose data handling, availability record, or jurisdiction the seller cannot accept (e.g. for where its planned_delivery data lands, or whose uptime its paused inventory depends on).
  2. The seller's choices are: integrate and transmit regardless, or refuse the account commercially — an off-protocol, all-or-nothing lever. There is no typed, auditable way to say "we accept AAO-verified operators" or "these endpoints".
  3. Every future binding change repeats the same silent imposition: sync_governance re-syncs are processed, not negotiated, and the seller learns operational problems (an unreachable or slow agent stalling its execution checks — or its resumes) only in production.

Scope

One mechanism — declaration plus binding-time enforcement. No change to check_governance, token verification, threading, or any check-time wire shape.

Acceptance declaration — on adcp.governance_enforcement

The seller declares acceptance criteria inside the existing cross-role adcp.governance_enforcement capability (static/schemas/source/protocol/get-adcp-capabilities-response.json — "Cross-role declaration that this agent enforces buyer-provided governance authorization when performing consequential tasks"). The acceptance declaration is a parameter of that enforcement claim — I enforce governance, against agents I accept — and, being cross-role, remains available to any seller role if governed commitments expand beyond media buying. It is deliberately not placed in the governance capabilities block, which is gated on governance in supported_protocols and therefore unavailable to ordinary sellers — the RFC's entire audience.

Acceptance is a typed any_of union of matcher objects — a binding is acceptable when any matcher accepts it:

{
  "adcp": {
    "governance_enforcement": {
      "accepted_governance_agents": {
        "any_of": [
          {
            "kind": "agent_url",
            "agent_url": "https://governance.neutral-operator.example"
          },
          {
            "kind": "verification",
            "registry": "https://registry.agenticadvertising.org",
            "role": "governance",
            "adcp_version": "3.2",
            "verification_modes": ["spec"],
            "max_age_seconds": 86400
          }
        ]
      }
    }
  }
}

Normative semantics:

  • The union is permissive by construction: a binding satisfying any one matcher MUST be accepted. NOTE: adding matchers widens acceptance — an agent_url entry plus a verification entry accepts more agents than either alone. Implementers wanting conjunctive restriction express it inside a single matcher, not across entries. The discriminated kind union makes this composition visible to validators and SDKs and is the extension point for future matcher kinds.
  • agent_url matcher (V1 floor): exact-match allowlisting under the canonicalization rules of docs/reference/url-canonicalization.mdx — the same rules that already govern accepted_verifiers comparisons.
  • verification matcher: deterministic contract — registry (authoritative lookup endpoint), role (required agent role in the registry), adcp_version (applicable version), verification_modes (accepted modes), max_age_seconds (freshness bound on cached verification state). If verification cannot be resolved (registry unreachable, stale beyond max_age_seconds with no refresh), the binding attempt fails with GOVERNANCE_UNAVAILABLE-class retryable semantics — the same principle the spec already applies to an unreachable agent at check time — and MUST NOT be reported as a definitive GOVERNANCE_AGENT_NOT_ACCEPTED. Acceptable alternative resolution: land V1 with the agent_url matcher only and sever the verification matcher to a follow-up RFC once registry governance is settled; either outcome satisfies this RFC.
  • Non-discrimination: declared acceptance criteria MUST reflect objective, non-discriminatory eligibility standards (operator verification status, data-handling certification, availability commitments) — not commercial leverage over particular counterparties.

Enforcement at sync_governance

A sync_governance call naming an agent outside the seller's acceptance fails with the typed error GOVERNANCE_AGENT_NOT_ACCEPTED (mirroring PROVENANCE_VERIFIER_NOT_ACCEPTED), carrying the applicable acceptance criteria in error.details so the buyer can select a compliant agent without out-of-band discovery.

  • Scoping — advisory declaration, authoritative response. get_adcp_capabilities has no account input, so the capabilities declaration is a seller-wide advisory default for discovery. The sync_governance response is authoritative per account: a seller MAY reject a binding on account-specific grounds (e.g. a regulated-category account requiring stricter criteria), carrying the account-applicable criteria in the error details. This mirrors the discovery-advisory / response-authoritative pattern RFC: structured seller acceptance-policy discovery backed by the policy registry #6749 establishes for acceptance policies.
  • Rejected-binding hygiene. A rejected binding MUST NOT be persisted, MUST NOT be contacted, and its credentials MUST NOT be echoed in any response or log surface.
  • Stability. Absent declaration = accept any (today's behavior, unchanged). An already-synced binding is unaffected by a later declaration change until the next sync_governance — no retroactive invalidation, the same stability rule as other capability declarations.

Non-goals

  • No bilateral selection, negotiation rounds, leader election, or propose/accept handshake (that territory belongs to RFC: unify governance behind a jointly-selected agent layer across domains (4.0 direction) #6657 if the WG ever takes it).
  • No seller authority over spend approval: the buyer still chooses the agent; the seller's declaration only bounds which choices it can integrate with — exactly as accepted_verifiers bounds creative verifiers.
  • No new obligations for sellers that declare nothing.

Alternatives considered

  1. Do nothing. The seller's only lever over an imposed integration counterparty remains refusing the account commercially; data-handling trust stays a one-sided SHOULD; recovery availability stays an unpriced dependency.
  2. Parallel agents[] / criteria[] fields with prose-defined OR (this RFC's originally filed shape). Rejected per review: the permissive composition is invisible in the schema, and implementers expecting additive restriction would misconfigure silently. The any_of matcher union expresses the same semantics structurally.
  3. Criteria-only (no URL allowlist). More neutral — avoids sellers hard-pinning vendors — but blocks the legitimate "we have an integration/DPA with these two operators" case, and a bare criteria string is not deterministic without the full verification contract. Both matcher kinds are specified instead.
  4. Handle out-of-band (commercial contracts). Where it lives today; leaves no typed error, nothing in capabilities for buyers to discover before attempting a binding, and no interoperable vocabulary across sellers.
  5. Full mutual agreement on the binding. The heavyweight form; deliberately out of scope per the spend-approval framing — the buyer's approval infrastructure is the buyer's to choose within the seller's declared acceptance bounds.

Compatibility impact

Non-breaking; changeset class MINOR. New optional object on the existing cross-role adcp.governance_enforcement capability; one additive error-code enum entry (GOVERNANCE_AGENT_NOT_ACCEPTED — visible to strict validators); the unresolvable-verification path reuses GOVERNANCE_UNAVAILABLE-class semantics rather than adding a code (final code choice per maintainers). sync_governance behavior changes only for sellers that declare acceptance; defaults are unchanged for everyone else.

Files affected (as of 17cb0f5 — re-verify at implementation)

  • static/schemas/source/protocol/get-adcp-capabilities-response.jsonaccepted_governance_agents on adcp.governance_enforcement
  • static/schemas/source/account/sync-governance-response.json — typed rejection path with criteria in details
  • static/schemas/source/enums/error-code.jsonGOVERNANCE_AGENT_NOT_ACCEPTED
  • docs/reference/url-canonicalization.mdx — new row for accepted_governance_agents.any_of[].agent_url vs bound agent URL
  • docs/accounts/tasks/sync_governance.mdx — acceptance semantics, hygiene rules, advisory/authoritative scoping
  • docs/governance/campaign/specification.mdx / safety-model.mdx — one-paragraph updates where the binding is described
  • Schema index, changeset file, compliance symbol registry entries; compliance storyboards as follow-up scope

Reviewer checklist

  • Motivation is clear and not redundant with existing functionality
  • Scope is specific enough to implement without further clarification
  • Alternatives section covers at least one non-obvious alternative
  • Compatibility impact accurately states breaking vs. non-breaking
  • Wire-format or schema snippet included (for schema or task changes)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.governanceIssue concerns the governance protocol domainrfcProtocol change — auto-adds to roadmap boardschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions