Skip to content

reporting: slice requests don't carry the owning consumer_id #1431

Description

@bokelley

Summary

9.0 adds caller ownership to the reporting ledger. Every configuration generation, obligation and status row is keyed by (account_id, consumer_id, …). The request a reporting source receives does not name that consumer. ReportingSourceSliceRequestV1.identity (ReportingSourceIdentityV1) carries account_id, delivery_config_id, delivery_config_version, reporting_obligation_id and source_scope, but no consumer_id.

Why it matters

Some sellers authorize delivery reads per caller: a buyer principal may only read its own media buys. Such a seller needs to know whose obligation it is fetching for. There are two ways to get that today:

  1. Put the consumer into source_scope. The account context resolver receives the ReportingConfiguration, which has consumer_id. This works, but source_scope is documented as the opaque place a source pins itself (provider, network, credential binding). The caller is an authorization fact, not a pin. Every adopter has to remember to add it. If one forgets, its source fetches with no caller, or with the wrong one, and nothing in the SDK notices.
  2. Fold the caller into account_id (for example tenant:account:principal). This is what we did before 9.0. It duplicates what caller ownership now does, and then responses have to be rewritten so buyers see their own account id.

A third option is to look the obligation up by reporting_obligation_id through the store. That costs an extra read per slice and depends on store internals.

Ask

Add consumer_id to ReportingSourceIdentityV1 (or to ReportingSourceSliceRequestV1), set from the obligation the slice is for. If it belongs in the logical slice fingerprint, fingerprint it there too.

Context

Hit while moving the Embedded Sales Agent (scope3data/embedded-sales-agent) to 9.0.0b1. It keeps option 2 for now (core/reporting.py, ledger_account_id). It would drop the fold once the slice request names the consumer.

Activity

  1. added
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 7, 2026
  2. bokelley commented on Oct 7, 2026

    @bokelley
    ContributorAuthor

    Triage

    Classification: Bug (SDK gap — consumer_id available at obligation construction time, not threaded through)
    Bucket(s): reporting (no matching label exists — flagging gap)
    Status: ready-for-human
    Milestone: (omit — no version label or milestoned PR)

    What the experts said:

    • code-reviewer: Add consumer_id: str (required) to ReportingSourceIdentityV1; populate from obligation.consumer_id in producer.py:1728; update two direct-construction sites: src/adcp/reporting/fixtures.py:238 and tests/test_reporting_source_contract.py:1006. Defer fingerprint change to avoid beta-phase churn — the field addition alone unblocks adopters.
    • ad-tech-protocol-expert: delivery_config_id is scoped to (account_id, consumer_id) per the 9.0 ledger model, so the current _logical_slice_fingerprint can collide across consumers today; 9.0.0b1 is the right window to fix it. ReportingSourceIdentityV1 is SDK-internal (not in published ADCP schema), so no spec PR needed.

    My take: Field addition and producer wiring are clear and unambiguous. Experts disagree on the fingerprint: the protocol expert flags a correctness collision risk that should land in beta; the code reviewer recommends deferral. The collision's practical impact depends on one fact you can confirm.

    One open question for @bokelley: Are media buy IDs globally unique (UUIDs or similar), or account-scoped integers? If globally unique, the fingerprint collision is impossible in practice and the field can ship as additive-only; if account-scoped, the fingerprint change is required for correctness and 9.0.0b1 is the right window.


    Triaged by Claude Code. Session: https://claude.ai/code/session_01Uz33hyBHDbRvRmVwdh3txp


    Generated by Claude Code

  3. added
    bugSomething isn't working
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 7, 2026
  4. bokelley commented on Oct 7, 2026

    @bokelley
    ContributorAuthor

    Triage

    Classification: Feature request
    Bucket(s): client (reporting subsystem — no reporting label exists in repo, gap noted)
    Status: ready-for-human

    What the experts said:

    • ad-tech-protocol-expert: consumer_id belongs in ReportingSourceIdentityV1 (not the outer request type — identity is sealed into manifests, so authorization facts belong there); non-breaking for production paths; do not add to logical_slice_fingerprint because delivery_config_id is consumer-scoped at provisioning so the fingerprint is already implicitly consumer-distinct, and adding it would silently invalidate stored fingerprint values; manifest-binding validation at producer.py:1596 must also check consumer_id or the authorization guarantee is incomplete.
    • code-reviewer: add as consumer_id: ExternalId | None = None (not required, to avoid breaking re-parsing of already-stored canonical manifests); do add to fingerprint — without it, two consumers who share the same obligation dimensions collapse into one dedup slot, silently suppressing re-dispatch for the distinct buyer; also needs fixtures.py:238, conformance.py:611 (_sequence_scope), source.py:1716 (publication_content_fingerprint_v1).

    My take: Both experts agree consumer_id belongs in ReportingSourceIdentityV1, set from obligation.consumer_id in producer.py:1718. The open question is _logical_slice_fingerprint (producer.py:1765): if delivery_config_id is guaranteed unique per consumer at provisioning, the fingerprint is already consumer-distinct and adding consumer_id there would only invalidate stored values with no benefit. If a delivery config can serve more than one consumer, consumer_id must be in the fingerprint.

    Question for @bokelley: Is delivery_config_id guaranteed unique per consumer_id in your provisioning model? That settles the fingerprint question.

    Implementation scope (agreed items):

    • src/adcp/reporting/source.py — add consumer_id: ExternalId | None = None after account_id in ReportingSourceIdentityV1
    • src/adcp/reporting/ledger/producer.py:1718 — pass consumer_id=obligation.consumer_id
    • src/adcp/reporting/ledger/producer.py:1596 — add consumer_id to manifest-binding validation
    • src/adcp/reporting/fixtures.py:238 — add consumer_id="consumer-redacted"
    • src/adcp/reporting/conformance.py:611 — add consumer_id to _sequence_scope
    • src/adcp/reporting/source.py:1716 — add consumer_id to publication_content_fingerprint_v1
    • Pending: fingerprint at producer.py:1765 — add "consumer_id": obligation.consumer_id only if delivery configs are not per-consumer unique

    Triaged by Claude Code. Session: https://claude.ai/code/session_012zvn8hi7cnExQXYxkP5k5x


    Generated by Claude Code

  5. added
    enhancementNew feature or request
    and removed
    bugSomething isn't working
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions