You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Status (2026-08-30): staged for ratification — one data model, three conformance tiers. The architecture below stands, but conformance splits into tiers and only Core is required. PR #6953 is draft until the merge gates hold.
Staging decision
Implementation review (5,295-line protocol PR; ~11k and ~15k-line SDK prototypes) showed this RFC had grown into three products. The model is preserved; conformance is tiered:
Tier
Capability
Contains
Required?
Core
reporting.core
Obligations, revisions, materializations, five health states, get_reporting_status — over transports sellers already have (get_media_buy_delivery, existing reporting_webhook). The reporting.delivery_ready doorbell is optional: a polling-only seller is fully Core-conformant.
Core alone solves the original problem — "where is yesterday's reporting, and is anything missing?" — and a basic webhook/polling seller implements it without understanding destinations, manifests, canonical hashing, or receipts.
An independent human protocol review approves the scope.
A minimal Core fixture demonstrates a polling-only seller passing reporting.core conformance with no destination, manifest, hashing, or receipt code.
Product goal
AdCP should make reliable reporting feel like one product surface:
A consumer asks “do I have definitive reporting for this period?”, gets a machine-checkable answer, and only involves a human when production, delivery, or reconciliation cannot recover automatically.
Moving bytes is not enough. The contract must distinguish:
what reports were supposed to exist;
what immutable logical content the seller published;
where each publication was materialized; and
whether each intended consumer independently observed matching data.
The SDK should orchestrate those facts. Applications should not build their own reporting state machine.
sync_accounts.reporting_delivery_configs[] selects and authorizes independently scheduled pacing, analytics, and billing feeds for one account.
reporting.delivery_ready is registered through existing account notification configuration and acts only as a signed, secret-free doorbell.
Existing reporting_webhook and get_media_buy_delivery remain the low-latency and compatibility data APIs.
The authenticated transport principal is the buyer/governance agent identity. It is not repeated as an untrusted payload field.
Agent-scoped reusable destinations are complementary, not a replacement for account configuration. The separate sync_agent_configuration proposal in #6966 lets a caller prove and register one BigQuery dataset, S3 prefix, Snowflake account, or Databricks recipient once for its relationship with a seller. Each account-level reporting configuration still authorizes which account, feed, media-buy scope, and schedule may use the resulting seller-issued destination reference. This allows consolidation across many accounts without making destination possession equivalent to data access.
Add one authoritative read task: get_reporting_status
The task has explicit views:
summary: operational health for an exact account/feed/time denominator;
periods: a stable cursor-paginated ledger used for complete reconciliation; and
Due obligations in an open scope are current and automatic delivery is working.
waiting
Nothing in the selected open scope is due yet.
delayed
Something is late but still inside automatic recovery.
action_required
An SLA/retry boundary was crossed and a structured responsible party/action is available.
complete
The scope is closed, retained, and every obligation satisfies finality, delivery, and any required consumer reconciliation.
Add one narrow write task: sync_reporting_receipts
SDK prototyping confirmed that the seller cannot infer consumer agreement from successful publication. This is especially clear for a buyer-owned destination: the seller may be allowed to write but correctly forbidden from reading it.
sync_reporting_receipts therefore records an authenticated consumer's accepted or rejected observation of one exact materialization. It is not a webhook acknowledgement. An accepted receipt includes row count and profile-defined control totals plus the selected verification evidence. A rejected receipt records stable mismatch codes. The consumer then reads the receipt back through get_reporting_status.
This is the minimum back-and-forth needed for the seller to know “the buyer says it has the same numbers.” It is still an attestation, not cryptographic proof that the buyer used those rows correctly after its stated downstream commit.
A replay mutation remains deferred. Retained immutable resources and the authoritative ledger provide ordinary missed-event recovery.
Capability declaration, snapshots, and mixed-source coverage
Managed delivery advertises exact reporting products, not a generic promise that a transport can carry every report.
The seller-wide media_buy.reporting_delivery.offerings[] catalog declares atomic combinations of report definition and row grain, feed purpose, cadence/alignment/SLA, supported snapshot and/or official finality, reconciliation profile, and delivery method. Hourly snapshots to object storage and daily official billing through a warehouse share are separate offerings.
Product.reporting_capabilities.reporting_delivery_offering_ids declares which seller-wide offerings packages using that product can satisfy. An empty list means none; omission means unknown, not universal support.
Account, seat, credential, provider, and API constraints may narrow eligibility during sync_accounts.
Each period obligation freezes the resulting media-buy and package coverage so later capability changes do not rewrite history.
A configuration selects coverage_requirement: full | allow_partial. Coverage is returned independently as full | partial | none | unknown, together with exact covered, unsupported, and unknown package/media-buy sets. A fresh, successfully delivered partial snapshot remains partial. Covered-subset metrics MUST NOT be presented as complete whole-buy totals, and implementations MUST NOT silently weaken the selected profile to a lowest common denominator.
Event-level or exposure-log access is a distinct reporting profile with its own privacy and authorization requirements. Advertising a clean-room or warehouse transport does not imply that profile is available.
Reliability data model
Obligation
One destination/configuration-specific report that should exist for an exact period, scope, finality, and due time. Obligations exist before delivery, which makes a missing first webhook or missing first file detectable.
Revision
One immutable logical publication. Revisions are deliberately destination-independent. They contain the report-definition/profile identity, account and media-buy scope, exact period, finality, observation/data-through boundary, row count, control totals, optional canonical content digest, and supersession lineage.
One canonical revision can fan out to S3, BigQuery, Databricks, Snowflake, a buyer agent, and a governance consumer without acquiring a different logical identity. A restatement creates a new revision and points to the immediately superseded revision. A zero-row revision is real and distinct from no revision.
Materialization
One attempt to expose a revision for one obligation and destination. Delivery configuration, destination, method, retry attempt, resource descriptor, and producer-side verification belong here. A retry creates a new materialization while preserving revision identity.
Receipt
One authenticated consumer's observation of one exact materialization. Buyer and governance consumers receipt independently; neither principal's result implies the other's.
Definitive-period algorithm
A buyer does not establish completeness by trusting one seller boolean. An SDK must:
derive the exact expected periods from its saved delivery-configuration generations and resolved calendar boundaries;
request that account/config/feed/time scope;
require scope_closed: true and full retained-ledger coverage;
exhaust a cursor bound to one immutable ledger_snapshot_id and ledger_as_of;
deduplicate obligation, revision, materialization, and receipt records by immutable IDs;
require the unique record count to equal pagination.total_count;
require each obligation's declared revision/materialization/receipt counts to equal the associated records actually returned;
require one obligation for every locally expected period;
select exactly one current revision and require the configured finality;
require a verified, unexpired exact materialization; and
when configured for consumer reconciliation, require an accepted matching receipt for that current revision/materialization.
This answers “do I have all of yesterday?” even when the first report never existed. For an open hour or month, the same surface reports current health and conservative data_through; it does not call an unfinished scope complete.
Totals are aggregated once per canonical revision, not once per destination, so fan-out cannot double-count spend or impressions.
Verification profiles
Full canonical hashing is valuable but too expensive to require for every frequent snapshot. Offerings select one explicit profile:
native_commit: immutable provider-native version/transaction evidence plus row count and control totals;
manifest_checksums: a digest-valid manifest plus every listed object's cryptographic checksum, row count, and control totals; or
canonical_digest: a digest of canonical logical rows under a content-addressed canonicalization contract.
Only canonical_digest claims exact logical equality across differently encoded/materialized copies. Billing requires official finality, canonical_digest, reconciliation_mode: consumer_receipt, and an accepted current receipt. Pacing and analytics may use cheaper profiles when the selected atomic offering allows them.
Normative file delivery
file_transfer uses a manifest-last commit. Producers write all immutable data objects first and publish the manifest last. Consumers ignore unlisted objects and do not process a materialization until the exact manifest digest validates.
The v1 manifest binds:
revision, obligation, materialization, and period identity;
format and compression;
the complete object list;
per-object sizes, row counts, and SHA-256/SHA-512 checksums;
total size and row count;
profile-defined control totals; and
creation time and complete: true.
This is the interoperability contract for S3/GCS/Azure-style delivery, rather than prose saying only “manifest last.”
Destination and orchestration semantics
The open method patterns are:
Pattern
Protocol boundary
file_transfer
A complete immutable manifest and all listed objects are readable and verified.
dataset_share
A versioned relation/share is readable through a representative intended consumer path.
warehouse_materialization
The exact revision is committed in the configured destination with the selected verification evidence.
producer_managed versus consumer_managed says who orchestrates delivery. It does not say who owns the table or which platform service writes it. GAM BigQuery Data Transfer can be consumer-managed even though the transfer service performs the write. A buyer-owned destination may end the seller's technical visibility at verified commit; the receipt reports the consumer's independent observation and optional non-secret downstream checkpoint.
Credentials, signed URLs, bearer profiles, passwords, and private keys never transit AdCP. Provider-native grants or explicit human setup handle access.
Fast path and reliable path
A short-period snapshot pacing feed gives the fast path. reporting.delivery_ready wakes the consumer quickly; polling repairs missed events.
Daily or billing-cycle official feeds give the slower authoritative path with longer SLA and retention.
Buyers may install pacing, analytics, and billing configurations simultaneously and reconcile each independently.
Event-level exposure logs are deferred until AdCP defines their privacy, authorization, purpose, minimization, retention, and recipient contract.
Campaign-control decisions based on feed health belong to buyer policy or Campaign Pulse. This reporting RFC exposes reliable evidence and never pauses or resumes spend itself.
Live and SDK validation
Live synthetic validation has exercised the two less-obvious destination paths:
BigQuery warehouse materialization: immutable A/B revisions, duplicate-safe commit, retained A after restatement B, exact replay, row-count and digest agreement.
Databricks Open Sharing: recipient-observed readiness, retained historical version reads, zero rows, revoke/regrant, and exact replay. The roughly 15-second recipient visibility delay proved that provider-side grant completion is not readiness. Open-client incompatibility with Delta Deletion Vectors proved that reader compatibility belongs in the offering.
The proposed schemas were then generated into current local checkouts of both SDKs:
JavaScript build and strict typecheck pass. Four end-to-end reconciliation tests pass: receipt write/readback with transient destination retry, missing expected-period detection, incomplete associated-history detection, and destination fan-out without double-counting.
Python Ruff and strict MyPy pass for the helper, and the same four reconciliation tests pass.
Python generation also exposed SDK work that must be fixed in its own PR: some current protocol models have renamed generated types still referenced by hand-written aliases, and canonical macro references need corrected $ref rebasing.
S3 uses standard object-store mechanics and is not a remaining schema-design gate, though a small live smoke test should become a conformance fixture before this experimental feature graduates.
Implementation
The first protocol/schema implementation is #6953. It includes the status and receipt tasks, four-record ledger, destination-independent revisions, normative manifest, verification profiles, delivery configuration/capabilities, signed readiness notification, security constraints, and contract tests.
The feature remains experimental while we gain multi-party production experience. The immediate follow-on is to land clean JavaScript and Python SDK PRs around the validated helper behavior, then exercise a seller adapter and an independent consumer/governance adapter end to end.
Staging decision
Implementation review (5,295-line protocol PR; ~11k and ~15k-line SDK prototypes) showed this RFC had grown into three products. The model is preserved; conformance is tiered:
reporting.coreget_reporting_status— over transports sellers already have (get_media_buy_delivery, existingreporting_webhook). Thereporting.delivery_readydoorbell is optional: a polling-only seller is fully Core-conformant.reporting.managed_deliveryreporting.reconciled_billingsync_reporting_receiptsCore alone solves the original problem — "where is yesterday's reporting, and is anything missing?" — and a basic webhook/polling seller implements it without understanding destinations, manifests, canonical hashing, or receipts.
Merge gates for #6953:
$refsfrom feat(protocol): add agent configuration sync #6966.reporting.coreconformance with no destination, manifest, hashing, or receipt code.Product goal
AdCP should make reliable reporting feel like one product surface:
Moving bytes is not enough. The contract must distinguish:
The SDK should orchestrate those facts. Applications should not build their own reporting state machine.
Minimal protocol surface
Reuse configuration and notifications
get_adcp_capabilitiesadvertises atomic managed-reporting offerings.sync_accounts.reporting_delivery_configs[]selects and authorizes independently scheduled pacing, analytics, and billing feeds for one account.reporting.delivery_readyis registered through existing account notification configuration and acts only as a signed, secret-free doorbell.reporting_webhookandget_media_buy_deliveryremain the low-latency and compatibility data APIs.The authenticated transport principal is the buyer/governance agent identity. It is not repeated as an untrusted payload field.
Agent-scoped reusable destinations are complementary, not a replacement for account configuration. The separate
sync_agent_configurationproposal in #6966 lets a caller prove and register one BigQuery dataset, S3 prefix, Snowflake account, or Databricks recipient once for its relationship with a seller. Each account-level reporting configuration still authorizes which account, feed, media-buy scope, and schedule may use the resulting seller-issued destination reference. This allows consolidation across many accounts without making destination possession equivalent to data access.Add one authoritative read task:
get_reporting_statusThe task has explicit views:
summary: operational health for an exact account/feed/time denominator;periods: a stable cursor-paginated ledger used for complete reconciliation; andrevision: exact retained revision/resource resolution.The five health states remain simple:
healthywaitingdelayedaction_requiredcompleteAdd one narrow write task:
sync_reporting_receiptsSDK prototyping confirmed that the seller cannot infer consumer agreement from successful publication. This is especially clear for a buyer-owned destination: the seller may be allowed to write but correctly forbidden from reading it.
sync_reporting_receiptstherefore records an authenticated consumer's accepted or rejected observation of one exact materialization. It is not a webhook acknowledgement. An accepted receipt includes row count and profile-defined control totals plus the selected verification evidence. A rejected receipt records stable mismatch codes. The consumer then reads the receipt back throughget_reporting_status.This is the minimum back-and-forth needed for the seller to know “the buyer says it has the same numbers.” It is still an attestation, not cryptographic proof that the buyer used those rows correctly after its stated downstream commit.
A replay mutation remains deferred. Retained immutable resources and the authoritative ledger provide ordinary missed-event recovery.
Capability declaration, snapshots, and mixed-source coverage
Managed delivery advertises exact reporting products, not a generic promise that a transport can carry every report.
media_buy.reporting_delivery.offerings[]catalog declares atomic combinations of report definition and row grain, feed purpose, cadence/alignment/SLA, supportedsnapshotand/orofficialfinality, reconciliation profile, and delivery method. Hourly snapshots to object storage and daily official billing through a warehouse share are separate offerings.Product.reporting_capabilities.reporting_delivery_offering_idsdeclares which seller-wide offerings packages using that product can satisfy. An empty list means none; omission means unknown, not universal support.sync_accounts.A configuration selects
coverage_requirement: full | allow_partial. Coverage is returned independently asfull | partial | none | unknown, together with exact covered, unsupported, and unknown package/media-buy sets. A fresh, successfully delivered partial snapshot remains partial. Covered-subset metrics MUST NOT be presented as complete whole-buy totals, and implementations MUST NOT silently weaken the selected profile to a lowest common denominator.Event-level or exposure-log access is a distinct reporting profile with its own privacy and authorization requirements. Advertising a clean-room or warehouse transport does not imply that profile is available.
Reliability data model
Obligation
One destination/configuration-specific report that should exist for an exact period, scope, finality, and due time. Obligations exist before delivery, which makes a missing first webhook or missing first file detectable.
Revision
One immutable logical publication. Revisions are deliberately destination-independent. They contain the report-definition/profile identity, account and media-buy scope, exact period, finality, observation/data-through boundary, row count, control totals, optional canonical content digest, and supersession lineage.
One canonical revision can fan out to S3, BigQuery, Databricks, Snowflake, a buyer agent, and a governance consumer without acquiring a different logical identity. A restatement creates a new revision and points to the immediately superseded revision. A zero-row revision is real and distinct from no revision.
Materialization
One attempt to expose a revision for one obligation and destination. Delivery configuration, destination, method, retry attempt, resource descriptor, and producer-side verification belong here. A retry creates a new materialization while preserving revision identity.
Receipt
One authenticated consumer's observation of one exact materialization. Buyer and governance consumers receipt independently; neither principal's result implies the other's.
Definitive-period algorithm
A buyer does not establish completeness by trusting one seller boolean. An SDK must:
scope_closed: trueand full retained-ledger coverage;ledger_snapshot_idandledger_as_of;pagination.total_count;This answers “do I have all of yesterday?” even when the first report never existed. For an open hour or month, the same surface reports current health and conservative
data_through; it does not call an unfinished scope complete.Totals are aggregated once per canonical revision, not once per destination, so fan-out cannot double-count spend or impressions.
Verification profiles
Full canonical hashing is valuable but too expensive to require for every frequent snapshot. Offerings select one explicit profile:
native_commit: immutable provider-native version/transaction evidence plus row count and control totals;manifest_checksums: a digest-valid manifest plus every listed object's cryptographic checksum, row count, and control totals; orcanonical_digest: a digest of canonical logical rows under a content-addressed canonicalization contract.Only
canonical_digestclaims exact logical equality across differently encoded/materialized copies. Billing requires official finality,canonical_digest,reconciliation_mode: consumer_receipt, and an accepted current receipt. Pacing and analytics may use cheaper profiles when the selected atomic offering allows them.Normative file delivery
file_transferuses a manifest-last commit. Producers write all immutable data objects first and publish the manifest last. Consumers ignore unlisted objects and do not process a materialization until the exact manifest digest validates.The v1 manifest binds:
complete: true.This is the interoperability contract for S3/GCS/Azure-style delivery, rather than prose saying only “manifest last.”
Destination and orchestration semantics
The open method patterns are:
file_transferdataset_sharewarehouse_materializationproducer_managedversusconsumer_managedsays who orchestrates delivery. It does not say who owns the table or which platform service writes it. GAM BigQuery Data Transfer can be consumer-managed even though the transfer service performs the write. A buyer-owned destination may end the seller's technical visibility at verified commit; the receipt reports the consumer's independent observation and optional non-secret downstream checkpoint.Credentials, signed URLs, bearer profiles, passwords, and private keys never transit AdCP. Provider-native grants or explicit human setup handle access.
Fast path and reliable path
reporting.delivery_readywakes the consumer quickly; polling repairs missed events.Campaign-control decisions based on feed health belong to buyer policy or Campaign Pulse. This reporting RFC exposes reliable evidence and never pauses or resumes spend itself.
Live and SDK validation
Live synthetic validation has exercised the two less-obvious destination paths:
The proposed schemas were then generated into current local checkouts of both SDKs:
$refrebasing.S3 uses standard object-store mechanics and is not a remaining schema-design gate, though a small live smoke test should become a conformance fixture before this experimental feature graduates.
Implementation
The first protocol/schema implementation is #6953. It includes the status and receipt tasks, four-record ledger, destination-independent revisions, normative manifest, verification profiles, delivery configuration/capabilities, signed readiness notification, security constraints, and contract tests.
The feature remains experimental while we gain multi-party production experience. The immediate follow-on is to land clean JavaScript and Python SDK PRs around the validated helper behavior, then exercise a seller adapter and an independent consumer/governance adapter end to end.