> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Reliable Reporting: implementing reporting.core

> The required Reliable Reporting tier: obligations, immutable closes, seller status, and an optional buyer-to-seller status loop — over the transports you already have.

<Warning>
  Reliable Reporting is experimental in 3.2. `reporting.core` is its required
  tier; `managed_delivery` and `reconciled_billing` are separately advertised
  optional tiers.

  The consumer-status loop is additive and opt-in in current source. Sellers
  advertise `consumer_status_task` only when implemented. The active notice makes
  it required Core in the next eligible minor after October 24, 2026; RC.1 and the
  current public training seller remain unchanged.
</Warning>

Every buyer eventually asks one question: **"do I have definitive reporting
for this period — and if not, whose problem is it?"** `reporting.core` makes
that question machine-answerable. Core is the required operational reporting
baseline: a seller must retain immutable revision evidence and expose its exact
binding over existing reporting transports. A seller that advertises the
consumer-status extension additionally records what the authenticated buyer
could actually consume. Core requires no destination, manifest, external
materialization, billing receipt, bespoke webhook, or outbound push code. It does require the
fixed revision-binding digest over canonical rows; this is distinct from the
Managed Delivery external-materialization canonicalization contract. Core carries
the operational cost of durable obligations, revision retention, and repair.

For an exact Core revision read, call `get_media_buy_delivery` with
`reporting_revision_id` and, optionally, an account selector. The returned `reporting_revision_binding`
must echo that ID and bind `{ reporting_revision_id, row_count, control_totals,
reporting_rows }` with RFC 8785 JCS and SHA-256. Exact reads are cursor-paginated:
every page repeats identical revision metadata and binding, and a consumer hashes
the concatenated rows in cursor order only after exhausting the frozen walk. Do
not substitute a fresh date-range pull for this read: it may have changed since
the immutable revision was published.

## What Core is

Four Core ideas, plus one opt-in status loop:

1. **Obligations exist before reports.** For every active delivery
   configuration and period, the seller records what *should* exist — so a
   missing first report is detectable, not silent.
2. **A zero-row report differs from no report.** An empty period commits a
   revision like any other; absence means something is wrong.
3. **Revisions are immutable logical content.** A provisional restatement is a
   new snapshot revision superseding the old snapshot, never an edit. An
   `official` revision is immutable billing-purpose evidence and cannot be
   superseded; later corrections are explicit accounting adjustments.
4. **[`get_reporting_status`](/dist/docs/3.2.0-rc.2/media-buy/task-reference/get_reporting_status)
   exposes seller state** — one authoritative read over the obligation ledger,
   summarized by five health states.
5. **When `consumer_status_task` is advertised,
   [`sync_reporting_status`](/dist/docs/3.2.0-rc.2/media-buy/task-reference/sync_reporting_status)
   closes the loop** — the buyer tells the seller whether an expected period
   was received, omitted, missing, or unreadable. Both attributed histories
   stay visible; neither overwrites the other.

| Health            | Meaning                                                              |
| ----------------- | -------------------------------------------------------------------- |
| `healthy`         | Everything due has been produced.                                    |
| `waiting`         | Nothing is due yet.                                                  |
| `delayed`         | Something due is late, automated recovery is still running.          |
| `action_required` | A human on the named `responsible_party` must act.                   |
| `complete`        | The queried scope is closed and every final obligation is satisfied. |

## Lifecycle at a glance

The reporting configuration creates the clock. Accepting a media buy does not
start a separate reporting SLA. Before a period closes, both sides can derive
its boundary and `next_expected_at`, but the seller cannot freeze an
`all_media_buys` denominator yet. At the period boundary, the obligation
becomes part of the authoritative ledger whether or not source data exists.

```mermaid theme={null}
%%{init: {"sequence": {"showSequenceNumbers": true}}}%%
sequenceDiagram
    accTitle: Reporting Core obligation and revision lifecycle
    accDescr: A buyer installs a reporting configuration. At period end the seller commits an obligation before acquiring source data, then commits an immutable revision. Managed delivery can optionally materialize and notify; status reads remain authoritative.
    participant Buyer as Buyer agent
    participant Seller as Seller agent
    participant Ledger as Reporting ledger
    participant Source as Reporting source
    participant Destination as Managed destination

    Buyer->>Seller: sync_accounts(reporting_delivery_configs)
    Seller-->>Buyer: Ready configuration + resolved schedule
    Note over Buyer,Seller: Both derive the next full period and expected_at

    Note over Seller,Ledger: period.end — freeze scope_resolved_at and media_buy_ids
    Seller->>Ledger: Commit obligation independently of source availability
    Buyer->>Seller: get_reporting_status
    Seller-->>Buyer: Obligation is visible (usually waiting)

    Seller->>Source: Acquire and normalize period data
    Source-->>Seller: Rows, including a valid zero-row result
    Seller->>Ledger: Commit immutable revision

    opt Source corrects an official revision later
        Seller->>Ledger: Commit immutable adjustment into an open accounting period
        Seller-->>Buyer: reporting.ledger_changed (optional)
    end

    alt Core
        Seller-->>Buyer: Revision readable through reporting API
    else Managed delivery
        Seller->>Destination: Materialize and verify revision
        Seller->>Ledger: Commit verified materialization
        Seller-->>Buyer: reporting.delivery_ready (optional)
    end

    opt Nothing satisfies the obligation by expected_at
        Seller->>Ledger: waiting → delayed
        Seller-->>Buyer: reporting.status_changed (optional)
    end

    Buyer->>Seller: get_reporting_status (authoritative repair)
    opt Seller advertises consumer_status_task
        Buyer->>Seller: sync_reporting_status (consumer status)
        Seller->>Ledger: Record buyer statement; compare current states
        opt Seller healthy but buyer cannot consume
            Seller->>Ledger: Open caller-scoped CONSUMER_STATUS_MISMATCH
        end
    end
```

This gives each side a distinct responsibility:

* **Seller:** commit every elapsed eligible period before taking a ledger
  snapshot after `period.end`, independently of source availability and before
  committing a revision. A matching `periods` view includes it in the
  paginated record set.
* **Buyer:** retain the accepted configuration generation, derive the same
  expected periods, and treat a missing obligation as a protocol failure rather
  than evidence that no activity occurred. When the seller advertises
  `consumer_status_task`, sync the resulting consumer status back and update it
  whenever consumption state changes.
* **Both:** calculate `expected_at` as `period.end + delivery_sla`. The
  obligation is committed at period close and appears in the first ledger
  snapshot strictly after it; the report becomes late only at
  `expected_at`.

For example, a configuration activated at `00:20Z` with hourly aligned periods
starts at the next full boundary. Its first period is `[01:00Z, 02:00Z)`. The
obligation appears in the first ledger snapshot strictly after `02:00Z`; with
`delivery_sla: PT1H`, it stays `waiting` through `03:00Z` and becomes
`delayed` after that if no revision has been produced. A snapshot exactly at
`02:00Z` does not expose it.

## What Core is not

* **No push requirement.** Polling `get_reporting_status` is the
  authoritative recovery path; a seller with no outbound push is fully
  conformant. When advertised, `sync_reporting_status` is an ordinary inbound
  task call, not a seller push or a buyer-hosted callback.
  If you do offer push, Core's doorbell is `reporting.status_changed` — an
  invalidation for health transitions in either direction, with stable
  `issue_ids`. `reporting.ledger_changed` is the separate optional invalidation
  for every new revision or adjustment, including changes that leave health
  untouched. `reporting.delivery_ready` belongs to the managed-delivery tier
  and never fires for Core configurations.
* **No delivery machinery.** Core offerings omit `method`: the status ledger
  and its immutable revision metadata are required, while any existing
  `get_media_buy_delivery` read or legacy reporting webhook remains an
  optional data transport. Core never requires push. Destinations, manifests,
  and provisioning belong to the `managed_delivery` tier.
* **No Managed canonicalization or billing receipts.** `reconciliation_mode` is
  `delivery_only`, offering profiles omit the external-materialization
  `canonicalization_*` contract, and `sync_reporting_receipts` does not exist
  for you. The opt-in `sync_reporting_status` extension instead echoes only the
  fixed revision-binding SHA-256 when reporting was received; it carries no row
  totals, Managed evidence, or billing acceptance. Receipts and Managed
  external materialization machinery belong to the `reconciled_billing` tier.

The boundary is executable: the `reporting-core` fixture test in the
protocol repository implements a complete polling-only seller and asserts
its source contains none of
`destination | manifest | billing receipt | readiness | webhook` (the Core binding
digest is intentionally retained).

## Build order for a seller

1. **Advertise the tier.** In `get_adcp_capabilities`, add
   `media_buy.reporting_delivery` with `supported: true`, `configuration_task`,
   `status_task`, at least one offering, `automated_recovery_window_seconds`,
   and `status_retention_days` — and add `media_buy.reporting_delivery` to
   `experimental_features`. Do not set `managed_delivery` or
   `reconciled_billing` until you implement them.
2. **Define one Core offering.** Feed purpose, an immutable
   `report_definition_id`, a content-addressed reporting profile (schema URI
   * SHA-256, grain, primary keys), a schedule (`period_duration`,
     `alignment`, `delivery_sla`), supported finality, and
     `reconciliation_mode: "delivery_only"`. No `method`.
3. **Generate obligations.** When an account installs a delivery
   configuration through [`sync_accounts`](/dist/docs/3.2.0-rc.2/accounts/tasks/sync_accounts),
   derive its future period boundaries and `next_expected_at`. At each eligible
   `period.end`, freeze the media-buy scope and commit that obligation
   independently of source availability and before committing its first
   revision. Every later ledger snapshot must record it, and a
   matching `get_reporting_status` `periods` view must return it, even when the
   first report never arrives. Campaign starts, stops, and configuration
   generations change *future* obligations, never past ones.
4. **Commit revisions.** When a period's data is final enough for its
   declared finality, record an immutable revision — including zero-row
   periods. Snapshot restatements create superseding revisions. Official
   revisions are terminal; publish any later source correction as a signed
   delta in a `reporting-adjustment` record applied to an open accounting
   period.
5. **Opt in to consumer status.** When implemented, advertise
   `consumer_status_task: "sync_reporting_status"` and accept it idempotently.
   Validate the expected period from the authenticated caller's immutable
   configuration generation even when no seller obligation exists. Retain
   superseded history and expose only that caller's statements back through the
   periods ledger.
6. **Project status.** Serve `get_reporting_status`: summary and periods
   views over a consistent ledger snapshot, health computed from obligations
   versus revisions versus the clock and current consumer status, `issues[]` carrying the typed code,
   `responsible_party`, and `recommended_action` whenever health is
   `delayed` or `action_required`.
7. **Escalate honestly.** A due obligation stays `delayed` while automated
   recovery runs, for at most `automated_recovery_window_seconds`; after
   that it is `action_required`. Never park a dead feed in `delayed`.

## Build order for a buyer

1. **Save the contract.** Persist every accepted reporting configuration
   generation, including its schedule, activation boundary, scope, report
   definition, coverage requirement, and required finality. Do not reconstruct
   historical expectations from a seller's current configuration.
2. **Derive expected periods.** Calculate the same half-open intervals and
   `expected_at` timestamps. A mid-period configuration activation begins at
   the next full boundary; deactivation still owes a period that already
   started.
3. **Use your own denominator.** For each elapsed expected period, require a
   matching obligation in the complete paginated `periods` view. The seller's
   returned obligation list cannot prove that the first one was not omitted.
4. **Close the history.** Keep one `ledger_snapshot_id` across every page,
   deduplicate immutable IDs, match the returned record counts, and distinguish
   a zero-row revision from no revision. An external billing system MAY retain
   an exact official `reporting_revision_id` as supporting evidence and apply
   later adjustments under its own controls. Apply managed-resource and receipt checks only when the selected
   tier requires them.
5. **Checkpoint repair.** After consuming every page, persist
   `changes_checkpoint`; send it as `changes_after` on the next periods read.
   Deduplicate safely replayed records by immutable ID.
6. **Sync consumer status when advertised.** If the seller declares
   `consumer_status_task`, call `sync_reporting_status` after bounded repair
   with the exact configuration generation, report definition, and period.
   Failures are sent promptly; later state changes use a new immutable status ID
   that supersedes the current leaf. Before closing a scope, ensure every elapsed
   expected period has a current status.
7. **Treat push as a wake-up.** `reporting.ledger_changed` starts a
   `changes_after` repair. Because a clock-only health transition may commit no
   ledger record, `reporting.status_changed` starts a non-incremental current
   summary or periods read. Poll periodically even when notifications are enabled.

## How to verify

* The **`reporting-core-declaration` universal storyboard** grades the
  proper-name Reliable Reporting 1.0 declaration and Core boundary. The
  capability-gated **`reliable-reporting-managed-delivery`** and
  **`reliable-reporting-reconciled-billing`** storyboards grade the optional
  tiers. The lifecycle-focused
  **`reporting-core` storyboard** uses `reporting_core_lifecycle_probe` to
  verify an obligation-before-report, clock-derived delayed health, and
  explicit zero-row publication. Sellers advertising `consumer_status_task`
  additionally run the capability-gated **`reporting-consumer-status`**
  storyboard for missing-obligation identity, mismatch, and supersession.
* The **reporting-core fixture test** is the tier boundary stated as code —
  copy its Core capability block and offering as your starting fixtures.
* Buyers verify the contract the same way with or without webhooks: they poll
  `get_reporting_status` and reconcile obligations against what they
  received.

Reliable Reporting 1.0 is published in the RC.1 wire bundle. On a deployment
that advertises `"3.2-rc.1"`, the public training sales agent serves the RC.1
reporting contract. Put the same negotiated version on every `sync_accounts`,
controller, and `get_reporting_status` request.

First confirm the public sales sandbox advertises `"3.2-rc.1"`, then call
`comply_test_controller` with
`scenario: "reporting_core_lifecycle_probe"` and `operation: "prepare"`. Use
the returned account and stable identifiers in a `get_reporting_status`
`periods` read. Then run `advance_time` with `target_health: "delayed"`, read
the summary, run `publish_zero_row`, and read the periods view again. The
[controller reference](/dist/docs/3.2.0-rc.2/building/by-layer/L3/comply-test-controller#reporting_core_lifecycle_probe)
contains the exact request shapes.

## The ladder above Core

Nothing in Core obligates you to climb, and each rung is separately
advertised:

* **`managed_delivery: true`** adds offerings with a `method` —
  file transfer, dataset shares, warehouse materialization — plus
  exact-resource retention and authorization-revocation bounds, bound to
  principal-layer destinations ([`sync_principal`](/dist/docs/3.2.0-rc.2/protocol/sync_principal)).
* **`reconciled_billing: true`** adds `receipt_task`
  ([`sync_reporting_receipts`](/dist/docs/3.2.0-rc.2/media-buy/task-reference/sync_reporting_receipts)),
  `consumer_receipt` reconciliation, and the canonical-digest contract —
  bilateral agreement on exact logical content, for billing.

Start with Core. It solves the problem everyone actually has.

## File-transfer object and version references

For Managed Delivery file transfers, every manifest `files[]` entry carries a
credential-free, destination-relative `object_ref`. For an S3 destination,
that means the decoded object key relative to the configured destination — not
an `s3://` URI and not a key with `?versionId=` appended. The object key has a
1,024-character schema bound, which accommodates S3's 1,024-UTF-8-byte key
limit; JSON Schema measures characters, not bytes.

When a provider has an immutable object version, place its decoded native
value in the entry's optional `native_version_ref`, separately from the key.
This covers S3 VersionIds as well as GCS generations and provider-native
snapshots. A VersionId can include characters such as `+`, `/`, and `=` that
expand when URI encoded. Keep the decoded value in the manifest and encode it
only while constructing the provider request. The same destination-relative
`object_ref` is used by `physical_checksums[]`, so it identifies the same
object without carrying credentials, signed URLs, or query parameters.

`reporting-file-manifest.json` contains the `files[]` entries:

```json theme={null}
{
  "files": [
    {
      "object_ref": "reports/2026-08-26/part-000.parquet",
      "native_version_ref": "3/L4kqtJlcpXroDTDmJ+rmSpXd3aIbrHY+",
      "size_bytes": 128,
      "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "row_count": 0
    }
  ]
}
```

`reporting-verification.json` contains the `physical_checksums[]` entries:

```json theme={null}
{
  "physical_checksums": [
    {
      "object_ref": "reports/2026-08-26/part-000.parquet",
      "algorithm": "sha256",
      "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    }
  ]
}
```
