Module adcp.reporting.ledger

Reliable Reporting reporting.core, seller side.

Core makes one question machine-answerable: do I have definitive reporting for this period – and if not, whose problem is it? Four ideas do the work.

  1. Obligations exist before reports. At each period close the seller freezes the scope and commits an obligation whether or not source data exists, so a missing first report is detectable rather than 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. A provisional restatement is a new snapshot superseding the old one. An official revision is terminal; later corrections are explicit accounting adjustments.
  4. get_reporting_status answers "where am I?" – one authoritative read over the ledger, summarized by five health states.

What You Assemble

::

from psycopg_pool import AsyncConnectionPool
from adcp.reporting.ledger import (
    PgReportingLedgerStore, ProducerOfferings, ReportingProducer,
    ReportingStatusCaller, ReportingStatusHandler,
)

store = PgReportingLedgerStore(pool=pool)
await store.create_schema()

producer = ReportingProducer(
    source=my_executor,                    # adcp.reporting.source
    offerings=ProducerOfferings(official_offering_id="DAILY_OFFICIAL_V1"),
    store=store,
)
await producer.run_worker()                # from cron, a loop, a supervisor

status = ReportingStatusHandler(store)
payload = await status.handle(
    {"view": "summary"},
    caller=ReportingStatusCaller(account_id=..., consumer_id=...),
)

No Temporal, no Celery, no Redis lock. Durability lives in the store; the worker is a stateless leased turn, and the status handler is a pure projection you can mount from :mod:adcp.server or any framework.

Health is derived, never stored – a pure function of obligations, revisions, and the snapshot clock, so it cannot go stale and two readers of one snapshot cannot disagree.

Opt-in surface

:mod:adcp.reporting.ledger.consumer_status implements sync_reporting_status, an additive opt-in extension in AdCP 3.2.0-rc.2 that is off by default. See that module for what turning it on commits you to.

Sub-modules

adcp.reporting.ledger.consumer_status

sync_reporting_status ingest and the consumer-mismatch projection …

adcp.reporting.ledger.delivery

Optional durable seller contracts for future destination writers and receipt handlers …

adcp.reporting.ledger.delivery_changes

Consumer-scoped incremental reconciliation reads, independent of Core cursors.

adcp.reporting.ledger.delivery_models

Retained seller delivery/reconciliation facts. No provider clients or wire blobs …

adcp.reporting.ledger.delivery_pg

PostgreSQL reconciliation evidence with a separate, principal-qualified feed.

adcp.reporting.ledger.health

Deriving reporting health from immutable evidence and the clock …

adcp.reporting.ledger.models

The records a seller's reporting ledger retains …

adcp.reporting.ledger.notification_events

Pure event mappings, invoked only by an opted-in store's transaction.

adcp.reporting.ledger.notification_models

Closed logical notifications and typed, consumer-qualified status identities.

adcp.reporting.ledger.pg

PostgreSQL-backed :class:~adcp.reporting.ledger.store.ReportingLedgerStore …

adcp.reporting.ledger.producer

The seller-side loop: close a period, acquire it, commit it, restate it …

adcp.reporting.ledger.producer_progress

Optional bounded producer progress; the original ledger protocol stays intact.

adcp.reporting.ledger.provisional

Durable scheduling identities and immutable provisional observation metadata …

adcp.reporting.ledger.reconciliation_projection

Pure tier evidence over one captured private record history …

adcp.reporting.ledger.schedule

One captured-generation clock for producer obligations and status forecasts …

adcp.reporting.ledger.status

get_reporting_status: one authoritative read over the obligation ledger …

adcp.reporting.ledger.status_projection

Pure status projection shared by polling, source transactions and clock sweeps …

adcp.reporting.ledger.status_server

A real optional reporting status task for the SDK MCP/A2A handler surface.

adcp.reporting.ledger.status_snapshot

Optional status participants and connection-bound snapshot/lifecycle helpers.

adcp.reporting.ledger.store

The durable seam under the reporting producer and the status handler …

Functions

def adjustment_to_wire(adjustment: ReportingAdjustmentRecord) ‑> dict[str, typing.Any]
Expand source code
def adjustment_to_wire(adjustment: ReportingAdjustmentRecord) -> dict[str, Any]:
    """Complete immutable adjustment evidence, including reason_detail in its digest."""
    return {
        **adjustment_payload(adjustment),
        "canonical_adjustment_sha256": adjustment_sha256(adjustment),
    }

Complete immutable adjustment evidence, including reason_detail in its digest.

def aggregate_reporting_health(obligation_health: Iterable[ReportingHealth],
*,
scope_closed: bool,
coverage_complete: bool) ‑> Literal['healthy', 'waiting', 'delayed', 'action_required', 'complete']
Expand source code
def aggregate_reporting_health(
    obligation_health: Iterable[ReportingHealth],
    *,
    scope_closed: bool,
    coverage_complete: bool,
) -> ReportingHealth:
    """Roll obligation health up to a scope.

    Worst-case wins, with one addition: incomplete coverage is itself
    ``action_required`` regardless of the obligations underneath it.  A scope
    whose denominator is not fully known cannot honestly be called healthy --
    the obligations that *are* present may simply be the ones that happened to
    resolve.
    """
    values = list(obligation_health)
    if not coverage_complete:
        return "action_required"
    if "action_required" in values:
        return "action_required"
    if "delayed" in values:
        return "delayed"
    if scope_closed and all(value == "complete" for value in values):
        return "complete"
    if any(value in {"healthy", "complete"} for value in values):
        return "healthy"
    return "waiting"

Roll obligation health up to a scope.

Worst-case wins, with one addition: incomplete coverage is itself action_required regardless of the obligations underneath it. A scope whose denominator is not fully known cannot honestly be called healthy – the obligations that are present may simply be the ones that happened to resolve.

def check_issue_state_transition(current: str, requested: str) ‑> None
Expand source code
def check_issue_state_transition(current: str, requested: str) -> None:
    """Refuse any transition the spec's forward-only lifecycle forbids.

    Raises :class:`LedgerConflictError` rather than returning a bool so neither
    store can forget to act on the answer.
    """
    if requested == current:
        # Idempotent: re-acknowledging an acknowledged issue is a no-op, which
        # is what makes an operator retry safe.
        return
    if requested not in _ISSUE_STATE_SUCCESSORS.get(current, frozenset()):
        raise LedgerConflictError(
            "ISSUE_STATE_TRANSITION_INVALID",
            "issue_state moves only forward through open, acknowledged, then resolved or "
            f"waived; {current!r} -> {requested!r} is not permitted. A recurrence gets a new "
            "occurrence rather than reviving a retired one",
        )

Refuse any transition the spec's forward-only lifecycle forbids.

Raises :class:LedgerConflictError rather than returning a bool so neither store can forget to act on the answer.

def consumer_mismatch_issue_key(*,
account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
report_definition_id: str,
period_start: datetime,
period_end: datetime) ‑> str
Expand source code
def consumer_mismatch_issue_key(
    *,
    account_id: str,
    consumer_id: str,
    delivery_config_id: str,
    delivery_config_version: int,
    report_definition_id: str,
    period_start: datetime,
    period_end: datetime,
) -> str:
    """Identity of the *condition*, not of the statement that evidences it.

    Keyed on the logical status chain, deliberately not on
    ``reporting_status_id``. A buyer that supersedes one conflicting statement
    with another conflicting statement has not resolved anything, and rc.3
    anchors the escalation clock to the issue's ``opened_at``. Keying on the
    statement id would mint a fresh issue -- and a fresh clock -- on every
    re-file, letting an unresolved disagreement dodge escalation forever.

    Also deliberately not keyed on the obligation id: a chain that began as
    ``obligation_missing`` attaches to the repaired obligation later, and the
    spec requires that chain never be lost, forked, or reset.
    """
    generation = ReportingConfigurationGenerationKey(
        account_id=account_id,
        consumer_id=consumer_id,
        delivery_config_id=delivery_config_id,
        delivery_config_version=delivery_config_version,
    )
    payload = canonical_json_utf8_v1(
        [
            "core-consumer-status-mismatch-v1",
            generation.account_id,
            consumer_id,
            generation.delivery_config_id,
            generation.delivery_config_version,
            report_definition_id,
            _utc(period_start).isoformat(),
            _utc(period_end).isoformat(),
        ]
    )
    return "rpik_" + hashlib.sha256(payload).hexdigest()[:40]

Identity of the condition, not of the statement that evidences it.

Keyed on the logical status chain, deliberately not on reporting_status_id. A buyer that supersedes one conflicting statement with another conflicting statement has not resolved anything, and rc.3 anchors the escalation clock to the issue's opened_at. Keying on the statement id would mint a fresh issue – and a fresh clock – on every re-file, letting an unresolved disagreement dodge escalation forever.

Also deliberately not keyed on the obligation id: a chain that began as obligation_missing attaches to the repaired obligation later, and the spec requires that chain never be lost, forked, or reset.

def consumer_statement_conflicts(*,
current: ConsumerStatusRecord,
current_revision: ReportingRevisionRecord | None,
revisions: Sequence[ReportingRevisionRecord] = ()) ‑> bool
Expand source code
def consumer_statement_conflicts(
    *,
    current: ConsumerStatusRecord,
    current_revision: ReportingRevisionRecord | None,
    revisions: Sequence[ReportingRevisionRecord] = (),
) -> bool:
    """Whether this statement contradicts the seller's *record* of the period.

    Deliberately independent of the seller's health *label*. The two questions
    are different and conflating them is how an escalation clock gets reset:
    health answers "should this caller's view be degraded right now", while
    this answers "does the disagreement still stand" -- which is what decides
    whether the issue may be retired.

    The spec allows resolution two ways: the consumer supersedes with a
    statement that agrees, or the seller's own projection changes so the two no
    longer conflict. Both are record comparisons:

    * ``obligation_missing`` -- the obligation is in front of us, so the buyer's
      claim that the seller omitted the period is contradicted. Always conflicts.
    * ``revision_missing`` -- conflicts only while the seller actually has a
      current required revision. If the seller has none either, they agree.
    * ``unreadable`` -- conflicts while the named revision is still readable in
      the seller's record. A seller that has since marked it unreadable agrees.
    * ``content_mismatch`` -- conflicts while the named revision is still the
      one the seller requires, because that is the seller standing behind the
      content the buyer disputes.
    * ``received`` -- conflicts only once something superseded the revision the
      buyer named.
    """
    status = current.consumer_status
    if status == "obligation_missing":
        return True
    if status == "revision_missing":
        return current_revision is not None
    if status == "unreadable":
        named = _named_revision(current, revisions)
        return named is None or named.readable
    if status == "content_mismatch":
        return (
            current_revision is not None
            and current.reporting_revision_id == current_revision.reporting_revision_id
        )
    if status == "received":
        if current.reporting_revision_id is None:
            return False
        if (
            current_revision is not None
            and current.reporting_revision_id == current_revision.reporting_revision_id
        ):
            return False
        # Stale only if a restatement actually superseded it. With no
        # supersession and no current required revision there is nothing to
        # re-read, so nothing to forgive and nothing to page about.
        return _superseder(current.reporting_revision_id, revisions) is not None or (
            current_revision is not None
        )
    # No fallback return: every ``ConsumerStatusValue`` is handled above, so a
    # sixth status added to the schema fails mypy with "missing return
    # statement" rather than silently classifying itself as "no conflict".

Whether this statement contradicts the seller's record of the period.

Deliberately independent of the seller's health label. The two questions are different and conflating them is how an escalation clock gets reset: health answers "should this caller's view be degraded right now", while this answers "does the disagreement still stand" – which is what decides whether the issue may be retired.

The spec allows resolution two ways: the consumer supersedes with a statement that agrees, or the seller's own projection changes so the two no longer conflict. Both are record comparisons:

  • obligation_missing – the obligation is in front of us, so the buyer's claim that the seller omitted the period is contradicted. Always conflicts.
  • revision_missing – conflicts only while the seller actually has a current required revision. If the seller has none either, they agree.
  • unreadable – conflicts while the named revision is still readable in the seller's record. A seller that has since marked it unreadable agrees.
  • content_mismatch – conflicts while the named revision is still the one the seller requires, because that is the seller standing behind the content the buyer disputes.
  • received – conflicts only once something superseded the revision the buyer named.
def current_consumer_statement(statuses: Sequence[ConsumerStatusRecord]) ‑> ConsumerStatusRecord | None
Expand source code
def current_consumer_statement(
    statuses: Sequence[ConsumerStatusRecord],
) -> ConsumerStatusRecord | None:
    """This caller's one unsuperseded leaf, or ``None`` for an empty chain."""
    return next((item for item in statuses if not item.superseded), None)

This caller's one unsuperseded leaf, or None for an empty chain.

def current_required_revision(obligation: ReportingObligationRecord,
revisions: Sequence[ReportingRevisionRecord]) ‑> ReportingRevisionRecord | None
Expand source code
def current_required_revision(
    obligation: ReportingObligationRecord,
    revisions: Sequence[ReportingRevisionRecord],
) -> ReportingRevisionRecord | None:
    """Source-compatible wrapper. Corrupt and not-ready histories both return None.

    New callers should consume ``select_reporting_revision``'s discriminated
    result so corruption can be parked for repair instead of retried as absence.
    """
    result = select_reporting_revision(
        revisions,
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
        required_finality=obligation.required_finality,
    )
    return result.revision if result.kind == "selected" else None

Source-compatible wrapper. Corrupt and not-ready histories both return None.

New callers should consume select_reporting_revision()'s discriminated result so corruption can be parked for repair instead of retried as absence.

def derive_period(schedule: ReportingScheduleSpec,
*,
account_timezone: str,
ordinal: int,
activated_at: datetime | None = None) ‑> ReportingPeriodBoundary
Expand source code
def derive_period(
    schedule: ReportingScheduleSpec,
    *,
    account_timezone: str,
    ordinal: int,
    activated_at: datetime | None = None,
) -> ReportingPeriodBoundary:
    """Derive period ``ordinal`` from a schedule, the way both sides must.

    Both the buyer and the seller run this calculation independently, and a
    buyer treats a missing obligation as a protocol failure rather than
    evidence that nothing happened.  That only works if the derivation is
    identical, so it lives here rather than in each side's handler.

    Periods are anchored at the Unix epoch in the schedule's timezone (or at an
    explicit ``period_anchor`` for billing-cycle alignment), which is what makes
    "the next full boundary" unambiguous.  A configuration activated mid-period
    begins at the next boundary -- a partial first period would be reported as
    complete and understate delivery.
    """
    zone_name = schedule.timezone_name(account_timezone)
    sla = iso_duration_to_timedelta(schedule.delivery_sla)
    # Walk whole periods from the anchor in local wall-clock terms so a DST
    # transition shifts the instant without changing which period it is.
    start, end = _period_instants(schedule, account_timezone, ordinal)

    if activated_at is not None and _utc(activated_at) > start:
        raise ValueError(
            "a configuration activated mid-period owes its first obligation at the next "
            "full boundary; a partial first period would understate delivery"
        )
    return ReportingPeriodBoundary(
        # No "/": a period key travels into the source slice request, whose
        # identifier pattern is [A-Za-z0-9_.:-].
        period_key=f"{start.strftime('%Y-%m-%dT%H:%M:%SZ')}_{schedule.period_duration}",
        start=start,
        end=end,
        source_timezone=zone_name,
        expected_at=end + sla,
    )

Derive period ordinal from a schedule, the way both sides must.

Both the buyer and the seller run this calculation independently, and a buyer treats a missing obligation as a protocol failure rather than evidence that nothing happened. That only works if the derivation is identical, so it lives here rather than in each side's handler.

Periods are anchored at the Unix epoch in the schedule's timezone (or at an explicit period_anchor for billing-cycle alignment), which is what makes "the next full boundary" unambiguous. A configuration activated mid-period begins at the next boundary – a partial first period would be reported as complete and understate delivery.

def iso_duration_to_timedelta(value: str) ‑> datetime.timedelta
Expand source code
def iso_duration_to_timedelta(value: str) -> timedelta:
    """Parse the restricted ISO-8601 durations a reporting schedule may use.

    Days, hours, minutes, seconds only.  Months and years are deliberately
    unsupported: their length depends on a calendar, and a schedule whose
    period length depends on which month it is cannot be derived identically by
    both sides -- which is the whole point of publishing the schedule.
    """
    import re

    match = re.fullmatch(
        r"P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?",
        value,
    )
    if match is None or value in {"P", "PT"}:
        raise ValueError(
            f"{value!r} is not a supported reporting duration; use day/hour/minute/second "
            "components only (calendar months and years are not derivable identically "
            "by both parties)"
        )
    days, hours, minutes, seconds = match.groups()
    return timedelta(
        days=int(days or 0),
        hours=int(hours or 0),
        minutes=int(minutes or 0),
        seconds=float(seconds or 0),
    )

Parse the restricted ISO-8601 durations a reporting schedule may use.

Days, hours, minutes, seconds only. Months and years are deliberately unsupported: their length depends on a calendar, and a schedule whose period length depends on which month it is cannot be derived identically by both sides – which is the whole point of publishing the schedule.

def issue_id_for(kind: str, *parts: object) ‑> str
Expand source code
def issue_id_for(kind: str, *parts: object) -> str:
    """A stable, derived issue identity.

    Derived rather than stored because these conditions are monotone for one
    immutable obligation: once a qualifying revision is associated,
    ``REPORT_OVERDUE`` cannot recur.  That makes a stateless identity correct
    without claiming a general issue-lifecycle ledger -- and it means a
    consumer polling twice deduplicates on the same id both times.
    """
    digest = hashlib.sha256(canonical_json_utf8_v1([kind, *[str(part) for part in parts]])).digest()
    return "rpti_" + base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")[:32]

A stable, derived issue identity.

Derived rather than stored because these conditions are monotone for one immutable obligation: once a qualifying revision is associated, REPORT_OVERDUE cannot recur. That makes a stateless identity correct without claiming a general issue-lifecycle ledger – and it means a consumer polling twice deduplicates on the same id both times.

def issue_id_for_occurrence(issue_key: str, generation: int) ‑> str
Expand source code
def issue_id_for_occurrence(issue_key: str, generation: int) -> str:
    """The id for one *occurrence* of a stored, non-monotone condition.

    :func:`issue_id_for` is enough for conditions that cannot recur once
    satisfied.  A consumer mismatch can: the buyer supersedes, the seller
    restates, the disagreement clears and comes back.  AdCP 3.2.0-rc.3 requires
    a recurrence after retirement to get a *new* ``issue_id``, so identity has
    to include which occurrence this is -- the generation counter held by
    :class:`~adcp.reporting.ledger.models.ReportingIssueLifecycle`.

    Folding the generation into the digest rather than appending it keeps the
    id opaque, so nothing downstream can parse it back into "how many times
    has this buyer complained".
    """
    digest = hashlib.sha256(
        canonical_json_utf8_v1(["core-issue-occurrence-v1", issue_key, generation])
    ).digest()
    return "rpti_" + base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")[:32]

The id for one occurrence of a stored, non-monotone condition.

:func:issue_id_for() is enough for conditions that cannot recur once satisfied. A consumer mismatch can: the buyer supersedes, the seller restates, the disagreement clears and comes back. AdCP 3.2.0-rc.3 requires a recurrence after retirement to get a new issue_id, so identity has to include which occurrence this is – the generation counter held by :class:~adcp.reporting.ledger.models.ReportingIssueLifecycle.

Folding the generation into the digest rather than appending it keeps the id opaque, so nothing downstream can parse it back into "how many times has this buyer complained".

def issue_is_retirable(current: str) ‑> bool
Expand source code
def issue_is_retirable(current: str) -> bool:
    """Whether the projection may move this state to ``resolved``.

    ``waived`` is not retirable: it is already out of the projection by
    agreement, and overwriting that readable act with ``resolved`` is an edge
    the forward-only lifecycle forbids. Both stores consult this rather than
    each remembering the rule.
    """
    return "resolved" in _ISSUE_STATE_SUCCESSORS.get(current, frozenset())

Whether the projection may move this state to resolved.

waived is not retirable: it is already out of the projection by agreement, and overwriting that readable act with resolved is an edge the forward-only lifecycle forbids. Both stores consult this rather than each remembering the rule.

def materialization_to_wire(view: ReportingMaterializationView,
*,
obligation: ReportingObligationRecord) ‑> dict[str, typing.Any]
Expand source code
def materialization_to_wire(
    view: ReportingMaterializationView, *, obligation: ReportingObligationRecord
) -> dict[str, Any]:
    if view.attempt.scope.generation_key != obligation.generation_key or (
        view.attempt.scope.reporting_obligation_id != obligation.reporting_obligation_id
    ):
        unavailable()
    result = view.to_wire()
    result["feed_purpose"] = obligation.feed_purpose
    return result
def project_consumer_mismatch(*,
obligation: ReportingObligationRecord,
current_revision: ReportingRevisionRecord | None,
statuses: Sequence[ConsumerStatusRecord],
seller_health: ReportingHealth,
revisions: Sequence[ReportingRevisionRecord] = (),
ledger_as_of: datetime | None = None,
delivery_sla: timedelta = datetime.timedelta(0),
automated_recovery_window: timedelta = datetime.timedelta(seconds=21600),
escalation: ReportingDeliveryEscalation | None = None,
lifecycle: ReportingIssueLifecycle | None = None) ‑> ConsumerMismatch | None
Expand source code
def project_consumer_mismatch(
    *,
    obligation: ReportingObligationRecord,
    current_revision: ReportingRevisionRecord | None,
    statuses: Sequence[ConsumerStatusRecord],
    seller_health: ReportingHealth,
    revisions: Sequence[ReportingRevisionRecord] = (),
    ledger_as_of: datetime | None = None,
    delivery_sla: timedelta = timedelta(0),
    automated_recovery_window: timedelta = timedelta(hours=6),
    escalation: ReportingDeliveryEscalation | None = None,
    lifecycle: ReportingIssueLifecycle | None = None,
) -> ConsumerMismatch | None:
    """Compare the seller's projection with this caller's current statement.

    Returns a mismatch only when they genuinely conflict. Five conflict kinds,
    four of them immediate:

    * ``obligation_missing``, ``revision_missing``, ``unreadable``, and
      ``content_mismatch`` against an otherwise ``healthy``/``complete`` seller
      projection are ``action_required`` at once; and
    * ``received`` naming a revision the seller has since superseded is
      ``delayed`` until its grace deadline, then ``action_required``.

    The carve-out exists because that buyer consumed exactly what the seller
    required at the time and has not yet had a bounded chance to re-read.
    Paging a human the instant a seller restates a provisional revision would
    train buyers to ignore the signal.

    An advertised ``consumer_mismatch_escalation`` boundary takes precedence
    when the two windows overlap: past ``opened_at`` plus that window the issue
    is ``action_required`` with a ``contact_*`` action regardless of the grace
    deadline. ``wait_for_retry`` must not survive that boundary -- an
    unattended mismatch is an escalation, not a retry.

    Missing consumer status stays *unknown*. It never excuses seller reporting
    and never by itself degrades seller health; silence is surfaced only
    through ``obligation_counts.consumer_status_pending``.
    """
    current = current_consumer_statement(statuses)
    if current is None:
        return None
    if lifecycle is not None and lifecycle.issue_state == "waived":
        if waiver_covers_mismatch(lifecycle, current, obligation, current_revision):
            return None
        # A caller must settle the new occurrence before publishing. Never
        # treat a legacy/unrelated waiver as permission to clear this health.
        lifecycle = None
    if not consumer_statement_conflicts(
        current=current, current_revision=current_revision, revisions=revisions
    ):
        return None
    if seller_health not in {"healthy", "complete"}:
        # The seller already knows something is wrong and has said so. Adding a
        # second issue for the same condition would double-count it.
        #
        # Note this is *only* a suppression of the emission. The condition is
        # still open, so a caller must not read ``None`` here as "resolved" --
        # see :func:`consumer_statement_conflicts`.
        return None

    boundary = _utc(ledger_as_of) if ledger_as_of is not None else None
    severity: Literal["delayed", "action_required"] = "action_required"
    grace_deadline: datetime | None = None

    if current.consumer_status == "received":
        assert current.reporting_revision_id is not None  # the conflict test proved it
        grace_deadline = stale_received_grace_deadline(
            received_revision_id=current.reporting_revision_id,
            revisions=revisions,
            delivery_sla=delivery_sla,
            automated_recovery_window=automated_recovery_window,
        )
        if grace_deadline is not None and boundary is not None and boundary < grace_deadline:
            severity = "delayed"
        responsible: ResponsibleParty = "buyer"
        message = (
            "the authenticated consumer received revision "
            f"{current.reporting_revision_id} but the current required revision is "
            f"{current_revision.reporting_revision_id if current_revision else 'unknown'}; "
            "the consumer is working from a superseded restatement"
        )
    else:
        responsible = _responsible_for(current)
        message = _negative_message(current)

    opened_at = _utc(lifecycle.opened_at) if lifecycle is not None else boundary
    escalated = False
    window = escalation.consumer_mismatch_escalation if escalation is not None else None
    if window is not None and opened_at is not None and boundary is not None:
        # Precedence: the escalation boundary wins when it overlaps the grace
        # window. Measured from the issue's opened_at, never from this poll, so
        # re-emission cannot reset it.
        if boundary >= opened_at + window:
            severity = "action_required"
            escalated = True

    issue = _mismatch_issue(
        obligation,
        current,
        responsible_party=responsible,
        message=message,
        severity=severity,
        escalated=escalated,
        lifecycle=lifecycle,
        opened_at=opened_at,
    )
    published = lifecycle is None or lifecycle.published
    return ConsumerMismatch(issue=issue, severity=severity, published=published)

Compare the seller's projection with this caller's current statement.

Returns a mismatch only when they genuinely conflict. Five conflict kinds, four of them immediate:

  • obligation_missing, revision_missing, unreadable, and content_mismatch against an otherwise healthy/complete seller projection are action_required at once; and
  • received naming a revision the seller has since superseded is delayed until its grace deadline, then action_required.

The carve-out exists because that buyer consumed exactly what the seller required at the time and has not yet had a bounded chance to re-read. Paging a human the instant a seller restates a provisional revision would train buyers to ignore the signal.

An advertised consumer_mismatch_escalation boundary takes precedence when the two windows overlap: past opened_at plus that window the issue is action_required with a contact_* action regardless of the grace deadline. wait_for_retry must not survive that boundary – an unattended mismatch is an escalation, not a retry.

Missing consumer status stays unknown. It never excuses seller reporting and never by itself degrades seller health; silence is surfaced only through obligation_counts.consumer_status_pending.

def project_obligation_health(obligation: ReportingObligationRecord,
revisions: Sequence[ReportingRevisionRecord],
*,
ledger_as_of: datetime,
scope_closed: bool) ‑> ObligationProjection
Expand source code
def project_obligation_health(
    obligation: ReportingObligationRecord,
    revisions: Sequence[ReportingRevisionRecord],
    *,
    ledger_as_of: datetime,
    scope_closed: bool,
) -> ObligationProjection:
    """Classify one obligation's immutable evidence at the snapshot's clock."""
    boundary = _utc(ledger_as_of)
    selection = select_reporting_revision(
        revisions,
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
        required_finality=obligation.required_finality,
    )
    current = selection.revision if selection.kind == "selected" else None

    if selection.kind == "corrupt":
        return ObligationProjection(
            health="action_required",
            production_status="published" if revisions else "pending",
            issues=(
                ReportingIssue(
                    issue_id=issue_id_for(
                        "core-revision-history-corrupt-v2",
                        obligation.account_id,
                        obligation.reporting_obligation_id,
                    ),
                    code="HISTORY_UNAVAILABLE",
                    severity="action_required",
                    responsible_party="seller",
                    recommended_action="contact_seller",
                    reporting_obligation_id=obligation.reporting_obligation_id,
                    delivery_config_id=obligation.delivery_config_id,
                    delivery_config_version=obligation.delivery_config_version,
                    feed_purpose=obligation.feed_purpose,
                    media_buy_ids=obligation.media_buy_ids,
                    period_start=obligation.period.start,
                    period_end=obligation.period.end,
                    message=(
                        "The retained revision history is inconsistent; seller repair is required."
                    ),
                ),
            ),
            satisfied=False,
            current_revision=None,
        )

    if obligation.currency is None:
        return ObligationProjection(
            health="action_required",
            production_status="published" if revisions else "pending",
            issues=(
                ReportingIssue(
                    issue_id=issue_id_for(
                        "core-currency-unresolved-v1", obligation.reporting_obligation_id
                    ),
                    code="HISTORY_UNAVAILABLE",
                    severity="action_required",
                    responsible_party="seller",
                    recommended_action="contact_seller",
                    reporting_obligation_id=obligation.reporting_obligation_id,
                    delivery_config_id=obligation.delivery_config_id,
                    delivery_config_version=obligation.delivery_config_version,
                    feed_purpose=obligation.feed_purpose,
                    media_buy_ids=obligation.media_buy_ids,
                    period_start=obligation.period.start,
                    period_end=obligation.period.end,
                    message=(
                        "Currency was not retained for this legacy obligation; "
                        "verified historical evidence is required."
                    ),
                ),
            ),
            satisfied=False,
            current_revision=current,
        )

    if current is not None and current.readable:
        return ObligationProjection(
            health="complete" if scope_closed else "healthy",
            production_status="published",
            issues=(),
            satisfied=True,
            current_revision=current,
        )

    if current is not None:
        # A qualifying revision exists but nothing is readable. Core's promise
        # is retained readability, so this is a seller repair, not a wait.
        return ObligationProjection(
            health="action_required",
            production_status="published",
            issues=(_unreadable_issue(obligation, current.reporting_revision_id),),
            satisfied=False,
            current_revision=current,
        )

    if revisions:
        # Revisions exist but none meet the required finality: the seller has
        # published something, so production is not pending -- it is simply not
        # yet official.
        production_status: ReportingProductionStatus = "published"
    elif boundary < _utc(obligation.period.expected_at):
        production_status = "not_due"
    else:
        production_status = "pending"

    if boundary < _utc(obligation.period.expected_at):
        return ObligationProjection(
            health="waiting",
            production_status=production_status,
            issues=(),
            satisfied=False,
            current_revision=None,
        )

    health: ReportingHealth = (
        "delayed"
        if boundary < _utc(obligation.automated_recovery_deadline_at)
        else "action_required"
    )
    return ObligationProjection(
        health=health,
        production_status=production_status,
        issues=(_overdue_issue(obligation, health),),
        satisfied=False,
        current_revision=None,
    )

Classify one obligation's immutable evidence at the snapshot's clock.

def project_status_scope(value: StatusProjectionInput) ‑> StatusProjectionResult
Expand source code
def project_status_scope(value: StatusProjectionInput) -> StatusProjectionResult:
    """Project exact semantics and only deadlines that can change those semantics."""
    result, candidates = _project(value)
    if result.intents:
        return result
    for at in sorted(candidates):
        if at <= value.snapshot.as_of:
            continue
        future, _ = _project(replace(value, snapshot=replace(value.snapshot, as_of=at)))
        if future.intents or future.fingerprint != result.fingerprint:
            return replace(result, next_due_at=at)
    return result

Project exact semantics and only deadlines that can change those semantics.

def receipt_to_wire(record: ReportingReceiptRecord) ‑> dict[str, typing.Any]
Expand source code
def receipt_to_wire(record: ReportingReceiptRecord) -> dict[str, Any]:
    result = payload(record)
    result.pop("scope")
    result.pop("kind")
    if isinstance(record, ReportingRevisionReceiptRecord):
        result["reporting_obligation_id"] = record.scope.reporting_obligation_id
        result["observed_control_totals"] = totals_to_wire(record.observed_control_totals)
        if record.observed_canonical_content_digest is not None:
            result["observed_canonical_content_digest"] = (
                record.observed_canonical_content_digest.to_wire()
            )
    return {
        key: value
        for key, value in result.items()
        if value is not None and not (key == "rejection_codes" and not value)
    }
def reject_reserved_authoritative_party(configuration: ReportingConfiguration) ‑> None
Expand source code
def reject_reserved_authoritative_party(configuration: ReportingConfiguration) -> None:
    """Refuse ``authoritative_party: consumer`` before the generation is stored.

    AdCP 3.2.0-rc.3 reserves the value for a buyer-deposited billing revision
    task scoped to a later minor. No released minor defines that task, so a
    3.2 seller must reject it with ``UNSUPPORTED_FEATURE`` *before* the
    generation becomes ready and before any obligation exists -- and must not
    silently coerce it to ``seller``.

    Coercing would be the dangerous option: the buyer asked to be the
    authoritative counter for a billing feed and would get a seller-authoritative
    one, with every obligation, revision, and receipt in this ledger quietly
    attributed the wrong way round.
    """
    if configuration.authoritative_party == "consumer":
        raise LedgerConflictError(
            "UNSUPPORTED_FEATURE",
            "authoritative_party 'consumer' is reserved for the buyer-deposited billing "
            "revision task, which no released AdCP minor defines. This seller produces "
            "every revision; omit the field or set it to 'seller'. See "
            "https://github.com/adcontextprotocol/adcp/issues/7440",
        )

Refuse authoritative_party: consumer before the generation is stored.

AdCP 3.2.0-rc.3 reserves the value for a buyer-deposited billing revision task scoped to a later minor. No released minor defines that task, so a 3.2 seller must reject it with UNSUPPORTED_FEATURE before the generation becomes ready and before any obligation exists – and must not silently coerce it to seller.

Coercing would be the dangerous option: the buyer asked to be the authoritative counter for a billing feed and would get a seller-authoritative one, with every obligation, revision, and receipt in this ledger quietly attributed the wrong way round.

def require_single_currency(currencies: Iterable[str]) ‑> str
Expand source code
def require_single_currency(currencies: Iterable[str]) -> str:
    """Resolve trusted constituent currencies, rejecting an empty or mixed scope.

    Call this inside a currency resolver with historical account/media-buy
    values, before an obligation is committed. It does not convert money.
    """
    resolved = {validate_currency(value) for value in currencies}
    if not resolved:
        return require_frozen_currency(None)
    if len(resolved) != 1:
        raise ReportingCurrencyError(
            "MIXED_CURRENCY_SCOPE", "one reporting obligation cannot aggregate multiple currencies"
        )
    return next(iter(resolved))

Resolve trusted constituent currencies, rejecting an empty or mixed scope.

Call this inside a currency resolver with historical account/media-buy values, before an obligation is committed. It does not convert money.

def revision_content_sha256(*,
reporting_revision_id: str,
row_count: int,
control_totals: Sequence[tuple[str, str]],
reporting_rows: Sequence[dict[str, Any]],
control_total_evidence: Sequence[ReportingControlTotalRecord] | None = None) ‑> str
Expand source code
def revision_content_sha256(
    *,
    reporting_revision_id: str,
    row_count: int,
    control_totals: Sequence[tuple[str, str]],
    reporting_rows: Sequence[dict[str, Any]],
    control_total_evidence: Sequence[ReportingControlTotalRecord] | None = None,
) -> str:
    """The Core revision binding: JCS over the four bound fields, SHA-256.

    This is the digest ``get_media_buy_delivery`` echoes in
    ``reporting_revision_binding`` and the one a consumer independently
    recomputes from what it actually read.  It binds exactly
    ``{reporting_revision_id, row_count, control_totals, reporting_rows}`` --
    nothing about storage, materialization, or delivery, which is what keeps
    Core's digest distinct from the Managed Delivery canonicalization contract.

    New managed publishers supply ``control_total_evidence`` to bind the exact
    type/unit-bearing totals exposed by status and exact reads. Omitting it keeps
    the existing Core pair projection and all previously retained hashes intact.
    """
    totals = (
        [
            item.to_wire()
            for item in freeze_control_totals(tuple(control_total_evidence), tuple(control_totals))
        ]
        if control_total_evidence is not None
        else [{"name": name, "value": value} for name, value in control_totals]
    )
    return hashlib.sha256(
        canonical_json_utf8_v1(
            {
                "reporting_revision_id": reporting_revision_id,
                "row_count": row_count,
                "control_totals": totals,
                "reporting_rows": [dict(row) for row in reporting_rows],
            }
        )
    ).hexdigest()

The Core revision binding: JCS over the four bound fields, SHA-256.

This is the digest get_media_buy_delivery echoes in reporting_revision_binding and the one a consumer independently recomputes from what it actually read. It binds exactly {reporting_revision_id, row_count, control_totals, reporting_rows} – nothing about storage, materialization, or delivery, which is what keeps Core's digest distinct from the Managed Delivery canonicalization contract.

New managed publishers supply control_total_evidence to bind the exact type/unit-bearing totals exposed by status and exact reads. Omitting it keeps the existing Core pair projection and all previously retained hashes intact.

def revision_to_wire(revision: ReportingRevisionRecord,
*,
obligation: ReportingObligationRecord) ‑> dict[str, typing.Any]
Expand source code
def revision_to_wire(
    revision: ReportingRevisionRecord, *, obligation: ReportingObligationRecord
) -> dict[str, Any]:
    """Opt-in evidence projection; Core's mounted handler remains unchanged."""
    from adcp.reporting.ledger.status import _revision_to_wire

    if (
        revision.account_id != obligation.account_id
        or revision.reporting_obligation_id != obligation.reporting_obligation_id
    ):
        unavailable()
    result = _revision_to_wire(revision, obligation)
    result["control_totals"] = totals_to_wire(revision_control_totals(revision, obligation))
    if revision.canonical_content_digest is not None:
        result["canonical_content_digest"] = revision.canonical_content_digest.to_wire()
    return result

Opt-in evidence projection; Core's mounted handler remains unchanged.

def select_reporting_revision(revisions: Sequence[R],
*,
account_id: str,
reporting_obligation_id: str,
required_finality: str) ‑> ReportingRevisionSelected[~R] | ReportingRevisionNotReady | ReportingRevisionCorrupt
Expand source code
def select_reporting_revision(
    revisions: Sequence[R],
    *,
    account_id: str,
    reporting_obligation_id: str,
    required_finality: str,
) -> ReportingRevisionSelection[R]:
    """Linear-time, order-independent selection, including disconnected cycles.

    Only empty history or a missing required official is ordinary not-ready.
    The caller supplies every retained revision belonging to the obligation,
    without filtering by finality, readability, or existing materializations.
    A selected unreadable revision remains selected: repair it, never fall back.
    """
    if (
        type(account_id) is not str
        or type(reporting_obligation_id) is not str
        or any(
            type(r.account_id) is not str
            or type(r.reporting_obligation_id) is not str
            or r.account_id != account_id
            or r.reporting_obligation_id != reporting_obligation_id
            for r in revisions
        )
    ):
        return ReportingRevisionCorrupt("ownership_mismatch")
    if any(
        type(r.reporting_revision_id) is not str
        or not r.reporting_revision_id
        or (
            r.supersedes_reporting_revision_id is not None
            and type(r.supersedes_reporting_revision_id) is not str
        )
        for r in revisions
    ):
        return ReportingRevisionCorrupt("invalid_revision_identity")
    by_id = {r.reporting_revision_id: r for r in revisions}
    if len(by_id) != len(revisions):
        return ReportingRevisionCorrupt("duplicate_revision_id")
    if (
        type(required_finality) is not str
        or required_finality not in {"snapshot", "official"}
        or any(
            type(r.finality) is not str or r.finality not in {"snapshot", "official"}
            for r in revisions
        )
    ):
        return ReportingRevisionCorrupt("invalid_finality")
    if any(
        r.supersedes_reporting_revision_id is not None
        and r.supersedes_reporting_revision_id not in by_id
        for r in revisions
    ):
        return ReportingRevisionCorrupt("missing_predecessor")
    if any(
        r.supersedes_reporting_revision_id is not None
        and by_id[r.supersedes_reporting_revision_id].finality != r.finality
        for r in revisions
    ):
        return ReportingRevisionCorrupt("cross_finality_edge")
    if any(
        r.finality == "official" and r.supersedes_reporting_revision_id is not None
        for r in revisions
    ):
        return ReportingRevisionCorrupt("official_predecessor")
    officials = [r for r in revisions if r.finality == "official"]
    if len(officials) > 1:
        return ReportingRevisionCorrupt("multiple_officials")
    snapshots = [r for r in revisions if r.finality == "snapshot"]
    successors: dict[str, str] = {}
    for revision in snapshots:
        predecessor = revision.supersedes_reporting_revision_id
        if predecessor is not None:
            if predecessor in successors:
                return ReportingRevisionCorrupt("forked_snapshot_history")
            successors[predecessor] = revision.reporting_revision_id
    # Walk each component once. A unique-looking leaf cannot hide a cycle.
    visited: set[str] = set()
    for revision in snapshots:
        path: set[str] = set()
        node: str | None = revision.reporting_revision_id
        while node is not None and node not in visited:
            if node in path:
                return ReportingRevisionCorrupt("revision_cycle")
            path.add(node)
            node = by_id[node].supersedes_reporting_revision_id
        visited.update(path)
    roots = [r for r in snapshots if r.supersedes_reporting_revision_id is None]
    if snapshots and len(roots) != 1:
        return ReportingRevisionCorrupt("disconnected_snapshot_history")
    if not revisions:
        return ReportingRevisionNotReady("empty_history")
    if officials:
        return ReportingRevisionSelected(officials[0])
    if required_finality == "official":
        return ReportingRevisionNotReady("official_required")
    leaf = next(r for r in snapshots if r.reporting_revision_id not in successors)
    return ReportingRevisionSelected(leaf)

Linear-time, order-independent selection, including disconnected cycles.

Only empty history or a missing required official is ordinary not-ready. The caller supplies every retained revision belonging to the obligation, without filtering by finality, readability, or existing materializations. A selected unreadable revision remains selected: repair it, never fall back.

def stale_received_grace_deadline(*,
received_revision_id: str,
revisions: Sequence[ReportingRevisionRecord],
delivery_sla: timedelta,
automated_recovery_window: timedelta) ‑> datetime.datetime | None
Expand source code
def stale_received_grace_deadline(
    *,
    received_revision_id: str,
    revisions: Sequence[ReportingRevisionRecord],
    delivery_sla: timedelta,
    automated_recovery_window: timedelta,
) -> datetime | None:
    """When a stale ``received`` stops being excusable.

    The anchor is the ``created_at`` of the **first** revision that superseded
    the one the consumer named, plus the configuration generation's
    ``delivery_sla`` as exact elapsed UTC time. Anchoring to the first
    supersession rather than the newest one is what stops the window from
    becoming a hiding place: otherwise a seller could hold a genuinely
    unresolved mismatch below ``action_required`` by restating on a timer.

    When ``delivery_sla`` resolves to zero in any of its legal spellings, the
    seller uses ``automated_recovery_window`` instead, so a zero-SLA feed still
    yields a bounded re-read window rather than an instant escalation.

    Returns ``None`` when nothing superseded the named revision -- there is no
    staleness to forgive.
    """
    superseder = _superseder(received_revision_id, revisions)
    if superseder is None:
        return None
    window = delivery_sla if delivery_sla > timedelta(0) else automated_recovery_window
    return _utc(superseder.created_at) + window

When a stale received stops being excusable.

The anchor is the created_at of the first revision that superseded the one the consumer named, plus the configuration generation's delivery_sla as exact elapsed UTC time. Anchoring to the first supersession rather than the newest one is what stops the window from becoming a hiding place: otherwise a seller could hold a genuinely unresolved mismatch below action_required by restating on a timer.

When delivery_sla resolves to zero in any of its legal spellings, the seller uses automated_recovery_window instead, so a zero-SLA feed still yields a bounded re-read window rather than an instant escalation.

Returns None when nothing superseded the named revision – there is no staleness to forgive.

def validate_currency(value: object) ‑> str
Expand source code
def validate_currency(value: object) -> str:
    """Require an ISO 4217-shaped code, without coercion or a registry lookup."""
    if not isinstance(value, str) or re.fullmatch(r"[A-Z]{3}", value) is None:
        raise ReportingCurrencyError(
            "INVALID_CURRENCY", "reporting currency must be three uppercase ASCII letters"
        )
    return value

Require an ISO 4217-shaped code, without coercion or a registry lookup.

Classes

class ConsumerMismatch (issue: ReportingIssue,
severity: "Literal['delayed', 'action_required']",
published: bool = True)
Expand source code
@dataclass(frozen=True)
class ConsumerMismatch:
    """A consumer mismatch, with the severity it degrades the caller's view to.

    Severity is carried alongside the issue rather than read back off it so the
    caller cannot accidentally project ``delayed`` health from an
    ``action_required`` issue, or the reverse.
    """

    issue: ReportingIssue
    severity: Literal["delayed", "action_required"]
    #: Only published occurrences contribute health. A waived occurrence is
    #: omitted only while its exact statement and diagnosed conflict match
    #: the recorded bilateral waiver. Later disagreements are independent.
    published: bool = True

A consumer mismatch, with the severity it degrades the caller's view to.

Severity is carried alongside the issue rather than read back off it so the caller cannot accidentally project delayed health from an action_required issue, or the reverse.

Instance variables

var issue : ReportingIssue
var published : bool

Only published occurrences contribute health. A waived occurrence is omitted only while its exact statement and diagnosed conflict match the recorded bilateral waiver. Later disagreements are independent.

var severity : Literal['delayed', 'action_required']
class ConsumerStatusDisabledError (*args, **kwargs)
Expand source code
class ConsumerStatusDisabledError(RuntimeError):
    """The consumer-status ingest was called without being enabled."""

The consumer-status ingest was called without being enabled.

Ancestors

  • builtins.RuntimeError
  • builtins.Exception
  • builtins.BaseException
class ConsumerStatusIngest (store: ReportingLedgerStore,
enabled: bool | None = None,
clock: Callable[[], datetime] | None = None)
Expand source code
@dataclass(frozen=True)
class ConsumerStatusIngest:
    """Records authenticated consumer statements into the ledger.

    Framework-agnostic like :class:`~adcp.reporting.ledger.status.ReportingStatusHandler`:
    a request mapping plus an authenticated caller in, a response mapping out.
    """

    store: ReportingLedgerStore
    enabled: bool | None = None
    #: Supplies ``recorded_at`` -- when the seller durably recorded the
    #: statement -- and therefore the issue's ``opened_at``. Defaults to wall
    #: clock. A deployment whose ledger boundary comes from the database should
    #: pass the same source here, or an issue's escalation clock and the
    #: snapshot that reads it will disagree about what time it is.
    clock: Callable[[], datetime] | None = None

    def _require_enabled(self) -> None:
        active = CONSUMER_STATUS_ENABLED if self.enabled is None else self.enabled
        if not active:
            raise ConsumerStatusDisabledError(
                "sync_reporting_status is an additive opt-in extension. Set "
                "adcp.reporting.ledger.consumer_status.CONSUMER_STATUS_ENABLED = True, or "
                "pass enabled=True, and advertise consumer_status_task before accepting "
                "live statements."
            )

    async def handle(
        self, request: dict[str, Any], *, account_id: str, consumer_id: str
    ) -> dict[str, Any]:
        """Serve one ``sync_reporting_status`` batch.

        Returns one ``recorded`` / ``unchanged`` / ``failed`` result per
        submitted statement.  A failure in one statement does not fail the
        batch: a buyer reporting five periods should not lose four good
        statements to one stale supersession pointer.
        """
        self._require_enabled()

        statements = request.get("statuses") or []
        if not statements:
            raise LedgerConflictError(
                "EMPTY_BATCH", "a sync_reporting_status batch must carry at least one statement"
            )
        seen_chains: set[tuple[Any, ...]] = set()
        results: list[dict[str, Any]] = []
        for statement in statements:
            status_id = statement.get("reporting_status_id", "")
            problems = _validate_consumer_status_wire(statement)
            if problems:
                results.append(_failed(status_id, "INVALID_CONSUMER_STATUS", problems[0]))
                continue
            try:
                record = self._to_record(
                    statement,
                    account_id=account_id,
                    consumer_id=consumer_id,
                    recorded_at=self._now(),
                )
            except ValueError as error:
                results.append(_failed(status_id, "INVALID_CONSUMER_STATUS", str(error)))
                continue
            if record.chain_key in seen_chains:
                # "A batch contains at most one update for each logical status
                # chain" -- two updates in one batch have no defined order, so
                # the second would silently win or silently lose.
                results.append(
                    _failed(
                        status_id,
                        "DUPLICATE_CHAIN_IN_BATCH",
                        "a batch may carry at most one statement per logical status chain",
                    )
                )
                continue
            seen_chains.add(record.chain_key)
            try:
                # Answer "exact retry?" before validating anything, because
                # some of that validation is time-dependent. "content_mismatch
                # must name the revision the seller currently requires" has an
                # answer that changes when the seller restates, so a retry of a
                # statement this ledger already accepted would otherwise be
                # rejected for a reason that did not apply when it was first
                # recorded. The spec's result_mapping requires `unchanged`, and
                # a buyer retrying after a transport failure has no way to tell
                # a genuine rejection from that.
                replay = await self.store.resolve_consumer_status_replay(record)
                if replay is not None:
                    results.append({"result": "unchanged", "consumer_status": _to_wire(replay)})
                    continue
                await self._validate_against_configuration(record)
                from adcp.reporting.ledger.status_snapshot import ReportingStatusParticipant

                atomic = isinstance(self.store, ReportingStatusParticipant)
                if atomic and isinstance(self.store, ReportingStatusParticipant):
                    stored, recorded = await self.store.record_consumer_status_with_lifecycle(
                        record
                    )
                else:
                    stored, recorded = await self.store.record_consumer_status(record)
            except LedgerConflictError as error:
                results.append(_failed(status_id, error.code, str(error)))
                continue
            if recorded and not atomic:
                await self._open_issue_on_first_observation(stored)
            results.append(
                {
                    "result": "recorded" if recorded else "unchanged",
                    "consumer_status": _to_wire(stored),
                }
            )
        return {"status": "completed", "results": results}

    async def _open_issue_on_first_observation(self, stored: ConsumerStatusRecord) -> None:
        """Start the escalation clock when the statement arrives, not when polled.

        ``opened_at`` is "when the seller first observed this logical
        condition", and the seller observes it here -- a statement landing on
        this task *is* the observation. Deferring to the first
        ``get_reporting_status`` would tie the escalation clock to whether
        anyone happened to poll: a mismatch filed and never read would sit at
        ``opened_at = None`` indefinitely and could never cross
        ``consumer_mismatch_escalation_seconds``, which is precisely the
        unattended case the boundary exists for.

        Only opens; never retires and never changes an existing occurrence.
        ``ensure_issue_opened`` is idempotent, so a supersession that keeps the
        same condition open keeps the same ``opened_at``.
        """
        obligation: ReportingObligationRecord | None = None
        if stored.reporting_obligation_id is not None:
            obligation = await self.store.get_obligation(
                account_id=stored.account_id,
                reporting_obligation_id=stored.reporting_obligation_id,
            )
        if obligation is None:
            obligation = await self.store.find_obligation(
                account_id=stored.account_id,
                consumer_id=stored.consumer_id,
                delivery_config_id=stored.delivery_config_id,
                delivery_config_version=stored.delivery_config_version,
                period_start=stored.period_start,
                period_end=stored.period_end,
            )
        revisions = (
            await self.store.list_revisions(
                account_id=stored.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            if obligation is not None
            else ()
        )
        current = None
        if obligation is not None:
            selection = select_reporting_revision(
                revisions,
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
                required_finality=obligation.required_finality,
            )
            if selection.kind == "corrupt":
                return  # Corruption cannot establish or resolve consumer disagreement.
            if selection.kind == "selected":
                current = selection.revision
        if not consumer_statement_conflicts(
            current=stored, current_revision=current, revisions=revisions
        ):
            return
        key = consumer_mismatch_issue_key(
            account_id=stored.account_id,
            consumer_id=stored.consumer_id,
            delivery_config_id=stored.delivery_config_id,
            delivery_config_version=stored.delivery_config_version,
            report_definition_id=stored.report_definition_id,
            period_start=stored.period_start,
            period_end=stored.period_end,
        )
        seen: set[str] = set()
        while (
            issue := await self.store.get_issue(account_id=stored.account_id, issue_key=key)
        ) is not None and issue.issue_state == "waived":
            if issue.issue_id in seen or issue.issue_key != key:
                raise LedgerConflictError("STATUS_PROJECTION_UNAVAILABLE", "invalid waiver chain")
            seen.add(issue.issue_id)
            if waiver_covers_mismatch(issue, stored, obligation, current):
                return
            key = condition_after_waiver(issue)
        await self.store.ensure_issue_opened(
            issue_key=key,
            account_id=stored.account_id,
            consumer_id=stored.consumer_id,
            observed_at=stored.recorded_at,
        )

    async def _validate_against_configuration(self, record: ConsumerStatusRecord) -> None:
        """Resolve every identifier the statement names, within caller + account.

        Deliberately does *not* require an obligation to exist *when none is
        named*: the whole point of ``obligation_missing`` is that it is filed
        when the seller's ledger omitted the period, and the configuration
        generation is what makes that period legitimate.

        But an obligation or revision id the caller *does* supply must resolve.
        The spec's ``batch_identity`` rule is explicit that every optional
        obligation, revision, and superseded status MUST resolve within the
        authenticated caller and account or fail with the same unavailable
        result. Skipping that lets a statement name a foreign, nonexistent, or
        long-superseded revision and still be recorded -- and then projected as
        ``action_required`` against a seller who never published the thing
        being disputed.
        """
        configurations = await self.store.list_configurations(
            caller=ReportingDeliveryPrincipal(record.account_id, record.consumer_id),
            delivery_config_ids=[record.delivery_config_id],
        )
        generation = next(
            (item for item in configurations if item.generation_key == record.generation_key),
            None,
        )
        if generation is None:
            raise LedgerConflictError(
                "UNKNOWN_CONFIGURATION_GENERATION",
                f"{record.delivery_config_id}@{record.delivery_config_version} is not an "
                "accepted configuration generation for this account",
            )
        if generation.report_definition_id != record.report_definition_id:
            raise LedgerConflictError(
                "REPORT_DEFINITION_MISMATCH",
                "the statement names a different report definition than the configuration "
                "generation accepted; unlike reporting promises must not share a status chain",
            )
        if (record.seller_ledger_snapshot_id is None) != (record.seller_ledger_as_of is None):
            raise LedgerConflictError(
                "SELLER_SNAPSHOT_EVIDENCE_INCOMPLETE",
                "seller_ledger_snapshot_id and seller_ledger_as_of are present together or "
                "not at all",
            )
        await self._resolve_named_records(record)
        validate_consumer_status_timing(record, generation, as_of=self._now())

    async def _resolve_named_records(self, record: ConsumerStatusRecord) -> None:
        """Resolve the optional obligation and revision ids the caller supplied.

        Unknown, unauthorized, cross-account, and cross-caller identifiers all
        produce the same result, so this surface cannot be used as an oracle to
        discover which ids exist on another tenant.
        """
        obligation: ReportingObligationRecord | None = None
        if record.reporting_obligation_id is not None:
            obligation = await self.store.get_obligation(
                account_id=record.account_id,
                reporting_obligation_id=record.reporting_obligation_id,
            )
            if obligation is None:
                raise LedgerConflictError(
                    "LOOKUP_UNAVAILABLE",
                    f"reporting_obligation_id {record.reporting_obligation_id} does not "
                    "resolve for this caller and account",
                )
            if (
                obligation.generation_key != record.generation_key
                or obligation.report_definition_id != record.report_definition_id
                or _utc(obligation.period.start) != _utc(record.period_start)
                or _utc(obligation.period.end) != _utc(record.period_end)
            ):
                # The id resolves but describes a different period or
                # generation. Recording it would attach this chain to the wrong
                # obligation, which the immutability rule forbids.
                raise LedgerConflictError(
                    "OBLIGATION_IDENTITY_MISMATCH",
                    f"reporting_obligation_id {record.reporting_obligation_id} names a "
                    "different configuration generation, report definition, or period than "
                    "this statement",
                )

        if obligation is None:
            obligation = await self.store.find_obligation(
                account_id=record.account_id,
                consumer_id=record.consumer_id,
                delivery_config_id=record.delivery_config_id,
                delivery_config_version=record.delivery_config_version,
                period_start=record.period_start,
                period_end=record.period_end,
            )
        required = None
        if obligation is not None:
            revisions = await self.store.list_revisions(
                account_id=record.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            selection = select_reporting_revision(
                revisions,
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
                required_finality=obligation.required_finality,
            )
            if selection.kind == "corrupt":
                raise LedgerConflictError(
                    "HISTORY_UNAVAILABLE", "the revision history requires repair"
                )
            if selection.kind == "selected":
                required = selection.revision
        if record.reporting_revision_id is None:
            return

        revision = await self.store.get_revision(
            account_id=record.account_id, reporting_revision_id=record.reporting_revision_id
        )
        if revision is None or (
            obligation is not None
            and revision.reporting_obligation_id != obligation.reporting_obligation_id
        ):
            raise LedgerConflictError(
                "LOOKUP_UNAVAILABLE",
                f"reporting_revision_id {record.reporting_revision_id} does not resolve for "
                "this caller, account, and period",
            )

        if record.consumer_status != "content_mismatch" or obligation is None:
            return

        # ``content_mismatch`` is valid only against a revision the seller
        # *currently requires* for the period. Against a superseded revision the
        # dispute is already moot -- the seller has replaced the content -- and
        # recording it would degrade the caller's own view over bytes neither
        # party stands behind any more. The buyer's move there is to re-read and
        # either accept or dispute the current revision.
        if required is None or required.reporting_revision_id != record.reporting_revision_id:
            raise LedgerConflictError(
                "REVISION_NOT_CURRENTLY_REQUIRED",
                f"content_mismatch names revision {record.reporting_revision_id}, which is "
                "not the revision this seller currently requires for the period; re-read the "
                "current revision and file against that",
            )

    def _now(self) -> datetime:
        return _utc(self.clock() if self.clock is not None else datetime.now(timezone.utc))

    @staticmethod
    def _to_record(
        statement: dict[str, Any],
        *,
        account_id: str,
        consumer_id: str,
        recorded_at: datetime,
    ) -> ConsumerStatusRecord:
        from adcp.types import ReportingConsumerStatus

        parsed = ReportingConsumerStatus.model_validate(statement)
        period = parsed.period
        return ConsumerStatusRecord(
            reporting_status_id=parsed.reporting_status_id,
            # Identity comes from authenticated transport. A body that asserts
            # a buyer or consumer principal is ignored, not trusted.
            account_id=account_id,
            consumer_id=consumer_id,
            delivery_config_id=parsed.delivery_config_id,
            delivery_config_version=parsed.delivery_config_version,
            report_definition_id=parsed.report_definition_id,
            period_start=_utc(period.start),
            period_end=_utc(period.end),
            period_source_timezone=period.source_timezone,
            consumer_status=_narrow_recorded_status(parsed.consumer_status.value),
            status_as_of=_utc(parsed.status_as_of),
            recorded_at=recorded_at,
            supersedes_reporting_status_id=parsed.supersedes_reporting_status_id,
            reporting_obligation_id=parsed.reporting_obligation_id,
            reporting_revision_id=parsed.reporting_revision_id,
            observed_revision_content_sha256=parsed.observed_revision_content_sha256,
            failure_code=parsed.failure_code.value if parsed.failure_code else None,
            mismatch_code=parsed.mismatch_code.value if parsed.mismatch_code else None,
            consumer_commit_ref=parsed.consumer_commit_ref,
            seller_ledger_snapshot_id=parsed.seller_ledger_snapshot_id,
            seller_ledger_as_of=(
                _utc(parsed.seller_ledger_as_of) if parsed.seller_ledger_as_of else None
            ),
        )

Records authenticated consumer statements into the ledger.

Framework-agnostic like :class:~adcp.reporting.ledger.status.ReportingStatusHandler: a request mapping plus an authenticated caller in, a response mapping out.

Instance variables

var clock : collections.abc.Callable[[], datetime.datetime] | None

Supplies recorded_at – when the seller durably recorded the statement – and therefore the issue's opened_at. Defaults to wall clock. A deployment whose ledger boundary comes from the database should pass the same source here, or an issue's escalation clock and the snapshot that reads it will disagree about what time it is.

var enabled : bool | None
var store : ReportingLedgerStore

Methods

async def handle(self, request: dict[str, Any], *, account_id: str, consumer_id: str) ‑> dict[str, typing.Any]
Expand source code
async def handle(
    self, request: dict[str, Any], *, account_id: str, consumer_id: str
) -> dict[str, Any]:
    """Serve one ``sync_reporting_status`` batch.

    Returns one ``recorded`` / ``unchanged`` / ``failed`` result per
    submitted statement.  A failure in one statement does not fail the
    batch: a buyer reporting five periods should not lose four good
    statements to one stale supersession pointer.
    """
    self._require_enabled()

    statements = request.get("statuses") or []
    if not statements:
        raise LedgerConflictError(
            "EMPTY_BATCH", "a sync_reporting_status batch must carry at least one statement"
        )
    seen_chains: set[tuple[Any, ...]] = set()
    results: list[dict[str, Any]] = []
    for statement in statements:
        status_id = statement.get("reporting_status_id", "")
        problems = _validate_consumer_status_wire(statement)
        if problems:
            results.append(_failed(status_id, "INVALID_CONSUMER_STATUS", problems[0]))
            continue
        try:
            record = self._to_record(
                statement,
                account_id=account_id,
                consumer_id=consumer_id,
                recorded_at=self._now(),
            )
        except ValueError as error:
            results.append(_failed(status_id, "INVALID_CONSUMER_STATUS", str(error)))
            continue
        if record.chain_key in seen_chains:
            # "A batch contains at most one update for each logical status
            # chain" -- two updates in one batch have no defined order, so
            # the second would silently win or silently lose.
            results.append(
                _failed(
                    status_id,
                    "DUPLICATE_CHAIN_IN_BATCH",
                    "a batch may carry at most one statement per logical status chain",
                )
            )
            continue
        seen_chains.add(record.chain_key)
        try:
            # Answer "exact retry?" before validating anything, because
            # some of that validation is time-dependent. "content_mismatch
            # must name the revision the seller currently requires" has an
            # answer that changes when the seller restates, so a retry of a
            # statement this ledger already accepted would otherwise be
            # rejected for a reason that did not apply when it was first
            # recorded. The spec's result_mapping requires `unchanged`, and
            # a buyer retrying after a transport failure has no way to tell
            # a genuine rejection from that.
            replay = await self.store.resolve_consumer_status_replay(record)
            if replay is not None:
                results.append({"result": "unchanged", "consumer_status": _to_wire(replay)})
                continue
            await self._validate_against_configuration(record)
            from adcp.reporting.ledger.status_snapshot import ReportingStatusParticipant

            atomic = isinstance(self.store, ReportingStatusParticipant)
            if atomic and isinstance(self.store, ReportingStatusParticipant):
                stored, recorded = await self.store.record_consumer_status_with_lifecycle(
                    record
                )
            else:
                stored, recorded = await self.store.record_consumer_status(record)
        except LedgerConflictError as error:
            results.append(_failed(status_id, error.code, str(error)))
            continue
        if recorded and not atomic:
            await self._open_issue_on_first_observation(stored)
        results.append(
            {
                "result": "recorded" if recorded else "unchanged",
                "consumer_status": _to_wire(stored),
            }
        )
    return {"status": "completed", "results": results}

Serve one sync_reporting_status batch.

Returns one recorded / unchanged / failed result per submitted statement. A failure in one statement does not fail the batch: a buyer reporting five periods should not lose four good statements to one stale supersession pointer.

class ConsumerStatusRecord (reporting_status_id: str,
account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
report_definition_id: str,
period_start: datetime,
period_end: datetime,
period_source_timezone: str,
consumer_status: ConsumerStatusValue,
status_as_of: datetime,
recorded_at: datetime,
supersedes_reporting_status_id: str | None = None,
reporting_obligation_id: str | None = None,
reporting_revision_id: str | None = None,
observed_revision_content_sha256: str | None = None,
failure_code: str | None = None,
mismatch_code: ReportingMismatchCode | None = None,
consumer_commit_ref: str | None = None,
seller_ledger_snapshot_id: str | None = None,
seller_ledger_as_of: datetime | None = None,
superseded: bool = False)
Expand source code
@dataclass(frozen=True)
class ConsumerStatusRecord:
    """One authenticated consumer statement about what it could consume.

    Separately attributed, append-only, and never seller-authored evidence:
    ``received`` does not satisfy the seller's production health, and a
    conflicting statement degrades **only** the submitting caller's view.  See
    :mod:`adcp.reporting.ledger.consumer_status`.
    """

    reporting_status_id: str
    account_id: str
    consumer_id: str
    delivery_config_id: str
    delivery_config_version: int
    report_definition_id: str
    period_start: datetime
    period_end: datetime
    period_source_timezone: str
    consumer_status: ConsumerStatusValue
    status_as_of: datetime
    recorded_at: datetime
    supersedes_reporting_status_id: str | None = None
    reporting_obligation_id: str | None = None
    reporting_revision_id: str | None = None
    observed_revision_content_sha256: str | None = None
    failure_code: str | None = None
    # AdCP 3.2.0-rc.3. Present exactly when consumer_status is
    # ``content_mismatch``: the closed reason the consumed revision contradicts
    # a fact the accepted configuration generation already fixed. Agents
    # dispatch on this value, never on ``message`` prose.
    mismatch_code: ReportingMismatchCode | None = None
    consumer_commit_ref: str | None = None
    seller_ledger_snapshot_id: str | None = None
    seller_ledger_as_of: datetime | None = None
    superseded: bool = False

    def __post_init__(self) -> None:
        principal_reference(self.account_id)
        consumer_reference(self.consumer_id)

    @property
    def generation_key(self) -> ReportingConfigurationGenerationKey:
        return ReportingConfigurationGenerationKey(
            account_id=self.account_id,
            consumer_id=self.consumer_id,
            delivery_config_id=self.delivery_config_id,
            delivery_config_version=self.delivery_config_version,
        )

    @property
    def chain_key(self) -> tuple[str, str, str, int, str, str, str]:
        """The logical chain this statement belongs to.

        Deliberately keyed *without* the seller's obligation id.  Requiring it
        would make the first missing report invisible again, which is the exact
        failure this loop exists to surface.

        The flat shape is retained for compatibility with persisted statement
        digests. Use ``generation_key`` when joining configuration generations.
        """
        return (
            self.account_id,
            self.consumer_id,
            self.delivery_config_id,
            self.delivery_config_version,
            self.report_definition_id,
            _utc(self.period_start).isoformat(),
            _utc(self.period_end).isoformat(),
        )

One authenticated consumer statement about what it could consume.

Separately attributed, append-only, and never seller-authored evidence: received does not satisfy the seller's production health, and a conflicting statement degrades only the submitting caller's view. See :mod:adcp.reporting.ledger.consumer_status.

Instance variables

var account_id : str
prop chain_key : tuple[str, str, str, int, str, str, str]
Expand source code
@property
def chain_key(self) -> tuple[str, str, str, int, str, str, str]:
    """The logical chain this statement belongs to.

    Deliberately keyed *without* the seller's obligation id.  Requiring it
    would make the first missing report invisible again, which is the exact
    failure this loop exists to surface.

    The flat shape is retained for compatibility with persisted statement
    digests. Use ``generation_key`` when joining configuration generations.
    """
    return (
        self.account_id,
        self.consumer_id,
        self.delivery_config_id,
        self.delivery_config_version,
        self.report_definition_id,
        _utc(self.period_start).isoformat(),
        _utc(self.period_end).isoformat(),
    )

The logical chain this statement belongs to.

Deliberately keyed without the seller's obligation id. Requiring it would make the first missing report invisible again, which is the exact failure this loop exists to surface.

The flat shape is retained for compatibility with persisted statement digests. Use generation_key when joining configuration generations.

var consumer_commit_ref : str | None
var consumer_id : str
var consumer_status : Literal['received', 'obligation_missing', 'revision_missing', 'unreadable', 'content_mismatch']
var delivery_config_id : str
var delivery_config_version : int
var failure_code : str | None
prop generation_key : ReportingConfigurationGenerationKey
Expand source code
@property
def generation_key(self) -> ReportingConfigurationGenerationKey:
    return ReportingConfigurationGenerationKey(
        account_id=self.account_id,
        consumer_id=self.consumer_id,
        delivery_config_id=self.delivery_config_id,
        delivery_config_version=self.delivery_config_version,
    )
var mismatch_code : Literal['scope_media_buy_missing', 'coverage_short', 'metric_missing', 'schema_nonconformant', 'currency_mismatch', 'period_mismatch'] | None
var observed_revision_content_sha256 : str | None
var period_end : datetime.datetime
var period_source_timezone : str
var period_start : datetime.datetime
var recorded_at : datetime.datetime
var report_definition_id : str
var reporting_obligation_id : str | None
var reporting_revision_id : str | None
var reporting_status_id : str
var seller_ledger_as_of : datetime.datetime | None
var seller_ledger_snapshot_id : str | None
var status_as_of : datetime.datetime
var superseded : bool
var supersedes_reporting_status_id : str | None
class FixedCurrencyResolver (currency: str = 'USD')
Expand source code
@dataclass(frozen=True)
class FixedCurrencyResolver:
    """The backward-compatible single-currency default, also usable explicitly."""

    currency: str = "USD"

    def __post_init__(self) -> None:
        validate_currency(self.currency)

    def __call__(
        self, configuration: ReportingConfiguration, obligation: ReportingObligationRecord
    ) -> str:
        return self.currency

The backward-compatible single-currency default, also usable explicitly.

Instance variables

var currency : str
class InMemoryReportingLedgerStore (*, clock: Callable[[], datetime] | None = None, notifications: bool = False)
Expand source code
class InMemoryReportingLedgerStore:
    """Process-local reference store. Correct, ordered, and not durable.

    ``clock`` supplies change timestamps and the snapshot observation boundary,
    standing in for the database clock a durable store reads. Override it to
    place a test's ledger boundary at a deliberate instant.
    """

    def __init__(
        self, *, clock: Callable[[], datetime] | None = None, notifications: bool = False
    ) -> None:
        self._clock = clock or (lambda: datetime.now(timezone.utc))
        self._lock = asyncio.Lock()
        self._configurations: dict[ReportingConfigurationGenerationKey, ReportingConfiguration] = {}
        self._obligations: dict[str, ReportingObligationRecord] = {}
        self._obligation_by_period: dict[
            tuple[ReportingConfigurationGenerationKey, str, str], str
        ] = {}
        self._revisions: dict[str, ReportingRevisionRecord] = {}
        self._revision_identity: dict[str, str] = {}
        self._rows: dict[str, tuple[dict[str, Any], ...]] = {}
        self._restatement_checkpoints: dict[str, RestatementCheckpoint] = {}
        self._retry_schedules: dict[str, RetryScheduleEntry] = {}
        self._provisional_acquisitions: dict[tuple[str, str, int], ProvisionalAcquisition] = {}
        self._provisional_observations: dict[tuple[str, str, int], ProvisionalObservation] = {}
        self._adjustments: dict[str, ReportingAdjustmentRecord] = {}
        self._statuses: dict[tuple[str, str, str], ConsumerStatusRecord] = {}
        self._status_identity: dict[tuple[str, str, str], str] = {}
        self._changes: list[tuple[int, str, LedgerRecordKind, str, datetime]] = []
        self._change_owners: dict[int, str] = {}
        self._sequence = 0
        self._leases: dict[ReportingConfigurationGenerationKey, tuple[str, datetime]] = {}
        # When each generation was last handed to a worker, so releasing a
        # lease sends that generation to the back of the queue instead of
        # letting it win every turn.
        self._lease_turns: dict[ReportingConfigurationGenerationKey, int] = {}
        self._lease_turn = 0
        # Live occurrence per (account, issue_key), plus the retired generation
        # high-water mark so a recurrence never reuses an id.
        self._issues: dict[tuple[str, str], ReportingIssueLifecycle] = {}
        self._issue_generations: dict[tuple[str, str], int] = {}
        self._issue_status_scopes: dict[tuple[str, str], ReportingStatusScope] = {}
        self._notification_state: NotificationState | None = None
        self._status_notification_state: Any = None
        if notifications:
            from adcp.reporting.outbox.memory import NotificationState

            self._notification_state = NotificationState()
            self._notification_state.issue_scopes = self._issue_status_scopes

    @asynccontextmanager
    async def _mutation(self) -> AsyncIterator[None]:
        """Publish domain changes and notifications under one rollback boundary.

        Rollback also applies with notifications disabled. Newly initialized
        collections and sequence heads belong to the transaction too.
        """
        owner = (id(self), asyncio.current_task())
        if _MEMORY_TRANSACTION.get() == owner:
            yield
            return
        async with self._lock:
            before = deepcopy(
                {key: value for key, value in vars(self).items() if key not in {"_lock", "_clock"}}
            )
            token = _MEMORY_TRANSACTION.set(owner)
            try:
                dirty_start = len(self._notification_state.dirty) if self._notification_state else 0
                yield
                if self._notification_state is not None:
                    from adcp.reporting.ledger.status_snapshot import settle_memory_snapshot
                    from adcp.reporting.outbox.status import StatusBoundary

                    dirty = tuple(self._notification_state.dirty[dirty_start:])
                    transaction_id = str(uuid4())
                    for account_id in sorted({d.scope.account_id for d in dirty}):
                        snapshot = settle_memory_snapshot(self, account_id)
                        account_dirty = tuple(d for d in dirty if d.scope.account_id == account_id)
                        self._notification_state.boundaries.append(
                            StatusBoundary(
                                transaction_id,
                                max(d.sequence for d in account_dirty),
                                account_dirty,
                                snapshot,
                            )
                        )
            except BaseException:
                for key in set(vars(self)) - {"_lock", "_clock"} - set(before):
                    del vars(self)[key]
                vars(self).update(before)
                raise
            finally:
                _MEMORY_TRANSACTION.reset(token)

    @asynccontextmanager
    async def transaction(self) -> AsyncIterator[InMemoryReportingLedgerStore]:
        """Group several source mutations into one atomic, replayable boundary."""
        async with self._mutation():
            yield self

    @asynccontextmanager
    async def _source_publication(
        self, account_id: str, *, seals: ReportingSealStore | None = None
    ) -> AsyncIterator[None]:
        # The memory mutation lock serializes all accounts, including seals.
        async with self._mutation():
            yield

    def _record_notification(self, event: ReportingDomainEvent) -> None:
        if self._notification_state is not None:
            self._notification_state.enqueue(event)

    def _dirty_status(
        self,
        scope: ReportingStatusScope,
        reason: DirtyReason,
        before: ReportingStatusEvidence | None = None,
        after: ReportingStatusEvidence | None = None,
    ) -> None:
        if self._notification_state is not None:
            self._notification_state.mark_dirty(scope, reason, self._clock(), before, after)

    def _resolve_issue_scope(
        self,
        *,
        account_id: str,
        consumer_id: str | None,
        issue_id: str,
        status_scope: ReportingStatusScope | None,
    ) -> ReportingStatusScope:
        """Validate a requested scope refinement without touching retained state.

        Default-off stores deliberately keep no rollback copy, so every caller
        that moves an issue record must clear this check *before* mutating:
        PostgreSQL rolls the statement back, the reference store cannot.
        """
        existing = self._issue_status_scopes.get((account_id, issue_id))
        scope = (
            status_scope or existing or ReportingStatusScope(account_id, consumer_id=consumer_id)
        )
        if scope.account_id != account_id or scope.consumer_id != consumer_id:
            raise ReportingNotificationError("invalid_status_scope")
        validate_scope_refinement(existing, scope)
        if scope.generation_key is not None:
            configuration = self._configurations.get(scope.generation_key)
            if configuration is None or (
                scope.feed_purpose is not None and configuration.feed_purpose != scope.feed_purpose
            ):
                raise ReportingNotificationError("invalid_status_scope")
        if scope.reporting_obligation_id is not None:
            obligation = self._obligations.get(scope.reporting_obligation_id)
            if (
                obligation is None
                or obligation.account_id != scope.account_id
                or (
                    scope.generation_key is not None
                    and obligation.generation_key != scope.generation_key
                )
                or (
                    scope.feed_purpose is not None and obligation.feed_purpose != scope.feed_purpose
                )
            ):
                raise ReportingNotificationError("invalid_status_scope")
        return scope

    def _dirty_issue(
        self,
        issue: ReportingIssueLifecycle,
        status_scope: ReportingStatusScope | None,
        before: ReportingIssueLifecycle | None = None,
        *,
        enqueue: bool = True,
    ) -> None:
        key = (issue.account_id, issue.issue_id)
        existing = self._issue_status_scopes.get(key)
        scope = self._resolve_issue_scope(
            account_id=issue.account_id,
            consumer_id=issue.consumer_id,
            issue_id=issue.issue_id,
            status_scope=status_scope,
        )
        self._issue_status_scopes[key] = scope
        if enqueue and (before != issue or existing != scope):
            self._dirty_status(
                scope, "issue", issue_evidence(before) if before else None, issue_evidence(issue)
            )

    async def create_schema(self) -> None:
        return None

    def _append(
        self, account_id: str, kind: LedgerRecordKind, record_id: str, *, consumer_id: str
    ) -> None:
        self._sequence += 1
        self._changes.append((self._sequence, account_id, kind, record_id, self._clock()))
        self._change_owners[self._sequence] = consumer_id

    # -- configurations --------------------------------------------------

    async def put_configuration(self, configuration: ReportingConfiguration) -> None:
        reject_reserved_authoritative_party(configuration)
        async with self._mutation():
            key = configuration.generation_key
            existing = self._configurations.get(key)
            if existing is not None and existing.quarantined:
                raise LedgerConflictError(
                    "REPORTING_GENERATION_QUARANTINED", "operator reconciliation is required"
                )
            if existing is not None and _fingerprint(_config_payload(existing)) != _fingerprint(
                _config_payload(configuration)
            ):
                raise LedgerConflictError(
                    "CONFIGURATION_GENERATION_IMMUTABLE",
                    f"configuration {key.delivery_config_id}@{key.delivery_config_version} "
                    "already exists with different content for this account; "
                    "publish a new version instead of editing a retained generation",
                )
            self._configurations[key] = configuration
            changed = existing is None or configuration_lifecycle(
                existing
            ) != configuration_lifecycle(configuration)
            if changed and self._notification_state is not None:
                self._dirty_status(
                    ReportingStatusScope(configuration.account_id, configuration.generation_key),
                    "configuration",
                    before=configuration_evidence(existing) if existing is not None else None,
                    after=configuration_evidence(configuration),
                )

    async def list_configurations(
        self, *, caller: ReportingCaller, delivery_config_ids: Sequence[str] | None = None
    ) -> tuple[ReportingConfiguration, ...]:
        wanted = set(delivery_config_ids) if delivery_config_ids else None
        return tuple(
            configuration
            for configuration in self._configurations.values()
            if configuration.account_id == caller.account_id
            and configuration.consumer_id == caller.consumer_id
            and (wanted is None or configuration.delivery_config_id in wanted)
        )

    async def list_all_configurations(self) -> tuple[ReportingConfiguration, ...]:
        return tuple(
            sorted(
                (c for c in self._configurations.values() if not c.quarantined),
                key=lambda item: (
                    item.account_id,
                    item.consumer_id,
                    item.delivery_config_id,
                    item.delivery_config_version,
                ),
            )
        )

    # -- obligations -----------------------------------------------------

    async def commit_obligation(
        self, obligation: ReportingObligationRecord
    ) -> ReportingObligationRecord:
        async with self._mutation():
            key = (
                obligation.generation_key,
                _utc(obligation.period.start).isoformat(),
                _utc(obligation.period.end).isoformat(),
            )
            existing_id = self._obligation_by_period.get(key)
            if existing_id is not None:
                return self._obligations[existing_id]
            if obligation.reporting_obligation_id in self._obligations:
                raise LedgerConflictError(
                    "OBLIGATION_IDENTITY_CONFLICT",
                    "the obligation identifier already belongs to a different logical period",
                )
            if obligation.generation_key not in self._configurations:
                raise LedgerConflictError(
                    "UNKNOWN_CONFIGURATION_GENERATION", "generation is unavailable"
                )
            if self._configurations[obligation.generation_key].quarantined:
                raise LedgerConflictError(
                    "REPORTING_GENERATION_QUARANTINED",
                    "establish a new owned generation after operator reconciliation",
                )
            require_frozen_currency(obligation.currency)
            self._obligations[obligation.reporting_obligation_id] = obligation
            self._obligation_by_period[key] = obligation.reporting_obligation_id
            self._append(
                obligation.account_id,
                "obligation",
                obligation.reporting_obligation_id,
                consumer_id=obligation.consumer_id,
            )
            if self._notification_state is not None:
                self._dirty_status(
                    ReportingStatusScope.for_obligation(obligation),
                    "obligation",
                    after=ReportingStatusEvidence("obligation", obligation.reporting_obligation_id),
                )
            return obligation

    async def get_obligation(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> ReportingObligationRecord | None:
        found = self._obligations.get(reporting_obligation_id)
        return found if found and found.account_id == account_id else None

    async def find_obligation(
        self,
        *,
        account_id: str,
        consumer_id: str,
        delivery_config_id: str,
        delivery_config_version: int,
        period_start: datetime,
        period_end: datetime,
    ) -> ReportingObligationRecord | None:
        key = (
            ReportingConfigurationGenerationKey(
                account_id=account_id,
                consumer_id=consumer_id,
                delivery_config_id=delivery_config_id,
                delivery_config_version=delivery_config_version,
            ),
            _utc(period_start).isoformat(),
            _utc(period_end).isoformat(),
        )
        found = self._obligation_by_period.get(key)
        return (
            await self.get_obligation(account_id=account_id, reporting_obligation_id=found)
            if found
            else None
        )

    # -- revisions -------------------------------------------------------

    async def commit_revision(
        self, revision: ReportingRevisionRecord, rows: Sequence[dict[str, Any]]
    ) -> ReportingRevisionRecord:
        rows = tuple(deepcopy(row) for row in rows)
        # Checked first, exactly as PgReportingLedgerStore does: a caller whose
        # rows do not match its own declared count must get the same code from
        # both stores, not whichever invariant that store happens to reach.
        if revision.row_count != len(rows):
            raise LedgerConflictError(
                "ROW_COUNT_MISMATCH",
                f"revision declares {revision.row_count} rows but {len(rows)} were supplied",
            )
        validate_managed_revision_rows(revision, rows)
        async with self._mutation():
            identity = _revision_identity(revision)
            existing = self._revisions.get(revision.reporting_revision_id)
            if existing is not None:
                if existing.account_id != revision.account_id:
                    raise LedgerConflictError(
                        "REVISION_NOT_FOUND", "no such revision for this account"
                    )
                if self._revision_identity[revision.reporting_revision_id] != identity:
                    raise LedgerConflictError(
                        "REVISION_IMMUTABLE",
                        f"revision {revision.reporting_revision_id} already exists with "
                        "different content; a restatement is a new revision",
                    )
                return existing
            obligation = self._obligations.get(revision.reporting_obligation_id)
            if obligation is None or obligation.account_id != revision.account_id:
                raise LedgerConflictError(
                    "OBLIGATION_NOT_FOUND",
                    "a revision must attach to an obligation committed at the period close",
                )
            if self._configurations[obligation.generation_key].quarantined:
                raise LedgerConflictError(
                    "REPORTING_GENERATION_QUARANTINED", "legacy evidence is read-only"
                )
            validate_revision_currency(obligation, revision, rows)
            siblings = [
                item
                for item in self._revisions.values()
                if item.reporting_obligation_id == revision.reporting_obligation_id
            ]
            if revision.finality == "official" and any(
                item.finality == "official" for item in siblings
            ):
                raise LedgerConflictError(
                    "OFFICIAL_REVISION_TERMINAL",
                    "an official revision already exists for this obligation; publish a later "
                    "source correction as an adjustment",
                )
            if revision.supersedes_reporting_revision_id:
                self._require_current_leaf(revision, siblings)
            self._revisions[revision.reporting_revision_id] = revision
            self._revision_identity[revision.reporting_revision_id] = identity
            self._rows[revision.reporting_revision_id] = tuple(dict(row) for row in rows)
            self._append(
                revision.account_id,
                "revision",
                revision.reporting_revision_id,
                consumer_id=obligation.consumer_id,
            )
            if self._notification_state is not None:
                from adcp.reporting.ledger.notification_events import revision_event

                self._record_notification(
                    revision_event(revision, self._clock(), consumer_id=obligation.consumer_id)
                )
                self._dirty_status(
                    ReportingStatusScope.for_obligation(obligation),
                    "revision",
                    after=ReportingStatusEvidence(
                        "revision",
                        revision.reporting_revision_id,
                        readable=revision.readable,
                        supersedes_id=revision.supersedes_reporting_revision_id,
                    ),
                )
            return revision

    def _require_current_leaf(
        self, revision: ReportingRevisionRecord, siblings: Sequence[ReportingRevisionRecord]
    ) -> None:
        target = revision.supersedes_reporting_revision_id
        known = {item.reporting_revision_id for item in siblings}
        if target not in known:
            raise LedgerConflictError(
                "SUPERSEDES_UNKNOWN",
                f"revision {target} is not part of this obligation's chain",
            )
        already = {
            item.supersedes_reporting_revision_id
            for item in siblings
            if item.supersedes_reporting_revision_id
        }
        if target in already:
            raise LedgerConflictError(
                "SUPERSEDES_STALE",
                f"revision {target} has already been superseded; a stale pointer would fork "
                "the chain and let a successful retry erase a recorded restatement",
            )

    async def list_revisions(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> tuple[ReportingRevisionRecord, ...]:
        return tuple(
            item
            for item in self._revisions.values()
            if item.account_id == account_id
            and item.reporting_obligation_id == reporting_obligation_id
        )

    async def get_provisional_acquisition(
        self, *, account_id: str, reporting_obligation_id: str, ordinal: int
    ) -> ProvisionalAcquisition | None:
        return self._provisional_acquisitions.get((account_id, reporting_obligation_id, ordinal))

    async def reserve_provisional_acquisition(
        self, acquisition: ProvisionalAcquisition
    ) -> ProvisionalAcquisition:
        # Canonical round-trip also detaches all request collections.
        acquisition = ProvisionalAcquisition.from_wire(acquisition.to_wire())
        key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
        async with self._mutation():
            existing = self._provisional_acquisitions.get(key)
            if existing is not None:
                return existing
            obligation = self._obligations.get(acquisition.obligation_id)
            if obligation is None or obligation.account_id != acquisition.account_id:
                raise LedgerConflictError("OBLIGATION_NOT_FOUND", "unknown observation obligation")
            if not acquisition.binds(obligation):
                raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition generation differs")
            if any(
                item.account_id == acquisition.account_id
                and item.execution_key == acquisition.execution_key
                for item in self._provisional_acquisitions.values()
            ):
                raise LedgerConflictError(
                    "OBSERVATION_CONFLICT", "execution key is already reserved"
                )
            checkpoint = self._restatement_checkpoints.get(acquisition.obligation_id)
            expected = (
                checkpoint.next_observation
                if checkpoint
                else len(
                    await self.list_revisions(
                        account_id=acquisition.account_id,
                        reporting_obligation_id=acquisition.obligation_id,
                    )
                )
            )
            if acquisition.ordinal != expected:
                raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
            self._provisional_acquisitions[key] = acquisition
            return acquisition

    async def get_provisional_observation(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> ProvisionalObservation | None:
        observations = [
            value
            for (account, obligation, _), value in self._provisional_observations.items()
            if account == account_id and obligation == reporting_obligation_id
        ]
        return max(observations, key=lambda item: item.acquisition.ordinal, default=None)

    async def commit_provisional_observation(
        self,
        observation: ProvisionalObservation,
        revision: ReportingRevisionRecord,
        rows: Sequence[dict[str, Any]],
    ) -> ReportingRevisionRecord:
        acquisition = observation.acquisition
        key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
        if (
            revision.account_id != acquisition.account_id
            or revision.reporting_obligation_id != acquisition.obligation_id
            or revision.reporting_revision_id != observation.revision_id
        ):
            raise LedgerConflictError("OBSERVATION_CONFLICT", "observation identity differs")
        async with self._mutation():
            existing = self._provisional_observations.get(key)
            if existing is not None:
                if (
                    existing.acquisition != acquisition
                    or existing.revision_id != observation.revision_id
                ):
                    raise LedgerConflictError("OBSERVATION_CONFLICT", "observation replay differs")
                if existing.revision_id not in self._revisions:
                    raise LedgerConflictError(
                        "HISTORY_UNAVAILABLE", "observation revision is missing"
                    )
                # A matching observation identity does not make conflicting
                # publication content an idempotent replay.
                return await self.commit_revision(revision, rows)
            retained = self._provisional_acquisitions.get(key)
            if retained != acquisition:
                raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition was not reserved")
            checkpoint = self._restatement_checkpoints.get(acquisition.obligation_id)
            if checkpoint is not None and checkpoint.next_observation != acquisition.ordinal:
                raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
            committed = await self.commit_revision(revision, rows)
            await self.record_restatement_checkpoint(
                RestatementCheckpoint(
                    acquisition.account_id,
                    acquisition.obligation_id,
                    observation.checked_at,
                    acquisition.ordinal + 1,
                    observation.provisional_until,
                )
            )
            self._provisional_observations[key] = observation
            return committed

    async def get_restatement_checkpoint(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> RestatementCheckpoint | None:
        checkpoint = self._restatement_checkpoints.get(reporting_obligation_id)
        return checkpoint if checkpoint and checkpoint.account_id == account_id else None

    async def get_retry_schedule(self, *, scope_key: str) -> RetryScheduleEntry | None:
        return self._retry_schedules.get(scope_key)

    async def record_retry_schedule(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
        async with self._mutation():
            existing = self._retry_schedules.get(entry.scope_key)
            if existing is not None:
                assert entry.recorded_at is not None and existing.recorded_at is not None
                if _utc(entry.recorded_at) < _utc(existing.recorded_at):
                    return existing
                if entry.attempt > 0 and not entry.blocked:
                    if existing.blocked and not entry.replayed:
                        return existing
                    if entry.attempt < existing.attempt and not entry.replayed:
                        return existing
                    if not entry.replayed and not existing.blocked:
                        entry = replace(
                            entry,
                            retry_not_before=max(
                                entry.retry_not_before, existing.retry_not_before, key=_utc
                            ),
                        )
            stored = replace(entry, replayed=False)
            self._retry_schedules[entry.scope_key] = stored
            return stored

    async def record_restatement_checkpoint(
        self, checkpoint: RestatementCheckpoint
    ) -> RestatementCheckpoint:
        async with self._mutation():
            obligation = self._obligations.get(checkpoint.reporting_obligation_id)
            if obligation is None or obligation.account_id != checkpoint.account_id:
                raise LedgerConflictError(
                    "OBLIGATION_NOT_FOUND",
                    "a restatement checkpoint must attach to an obligation for this account",
                )
            existing = self._restatement_checkpoints.get(checkpoint.reporting_obligation_id)
            if existing is not None:
                if checkpoint.next_observation < existing.next_observation:
                    return existing
                if checkpoint.next_observation == existing.next_observation and _utc(
                    checkpoint.checked_at
                ) <= _utc(existing.checked_at):
                    return existing
            self._restatement_checkpoints[checkpoint.reporting_obligation_id] = checkpoint
            return checkpoint

    async def get_revision(
        self, *, account_id: str, reporting_revision_id: str
    ) -> ReportingRevisionRecord | None:
        found = self._revisions.get(reporting_revision_id)
        return found if found and found.account_id == account_id else None

    async def read_revision_rows(
        self,
        *,
        account_id: str,
        reporting_revision_id: str,
        cursor: str | None = None,
        limit: int = 500,
    ) -> ReportingRowPage:
        revision = await self.get_revision(
            account_id=account_id, reporting_revision_id=reporting_revision_id
        )
        if revision is None:
            raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
        rows = self._rows.get(reporting_revision_id, ())
        offset = revision_row_offset(cursor, reporting_revision_id, limit)
        window = rows[offset : offset + limit]
        has_more = offset + limit < len(rows)
        return ReportingRowPage(
            reporting_revision_id=reporting_revision_id,
            rows=tuple(deepcopy(row) for row in window),
            total_count=len(rows),
            has_more=has_more,
            cursor=(
                encode_cursor(
                    {"ownership": 2, "revision": reporting_revision_id, "offset": offset + limit}
                )
                if has_more
                else None
            ),
        )

    async def set_revision_readable(
        self, *, account_id: str, reporting_revision_id: str, readable: bool
    ) -> None:
        async with self._mutation():
            existing = self._revisions.get(reporting_revision_id)
            if existing is None or existing.account_id != account_id:
                raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
            if existing.readable == readable:
                return
            self._revisions[reporting_revision_id] = replace(existing, readable=readable)
            if self._notification_state is not None:
                obligation = self._obligations[existing.reporting_obligation_id]
                self._dirty_status(
                    ReportingStatusScope.for_obligation(obligation),
                    "readability",
                    ReportingStatusEvidence(
                        "revision", reporting_revision_id, readable=existing.readable
                    ),
                    ReportingStatusEvidence("revision", reporting_revision_id, readable=readable),
                )

    # -- adjustments -----------------------------------------------------

    async def commit_adjustment(
        self, adjustment: ReportingAdjustmentRecord
    ) -> ReportingAdjustmentRecord:
        async with self._mutation():
            existing = self._adjustments.get(adjustment.reporting_adjustment_id)
            if existing is not None:
                if existing.account_id != adjustment.account_id:
                    raise LedgerConflictError("ADJUSTMENT_UNAVAILABLE", "adjustment is unavailable")
                if existing != adjustment:
                    raise LedgerConflictError(
                        "ADJUSTMENT_IMMUTABLE", "adjustment content is immutable"
                    )
                return existing
            revision = self._revisions.get(adjustment.adjusts_reporting_revision_id)
            if revision is None or revision.account_id != adjustment.account_id:
                raise LedgerConflictError(
                    "REVISION_NOT_FOUND", "an adjustment must name a committed revision"
                )
            if revision.finality != "official":
                raise LedgerConflictError(
                    "ADJUSTMENT_REQUIRES_OFFICIAL",
                    "adjustments correct an official revision; restate a snapshot with a "
                    "superseding snapshot revision instead",
                )
            obligation = self._obligations[revision.reporting_obligation_id]
            if self._configurations[obligation.generation_key].quarantined:
                raise LedgerConflictError(
                    "REPORTING_GENERATION_QUARANTINED", "legacy evidence is read-only"
                )
            validate_adjustment_currency(
                self._obligations[revision.reporting_obligation_id], adjustment
            )
            self._adjustments[adjustment.reporting_adjustment_id] = adjustment
            self._append(
                adjustment.account_id,
                "adjustment",
                adjustment.reporting_adjustment_id,
                consumer_id=obligation.consumer_id,
            )
            if self._notification_state is not None:
                from adcp.reporting.ledger.notification_events import adjustment_event

                self._record_notification(
                    adjustment_event(
                        adjustment,
                        self._clock(),
                        consumer_id=self._obligations[revision.reporting_obligation_id].consumer_id,
                    )
                )
                self._dirty_status(
                    ReportingStatusScope.for_obligation(
                        self._obligations[revision.reporting_obligation_id]
                    ),
                    "adjustment",
                    after=ReportingStatusEvidence("adjustment", adjustment.reporting_adjustment_id),
                )
            return adjustment

    async def list_adjustments(
        self, *, account_id: str, reporting_revision_ids: Sequence[str]
    ) -> tuple[ReportingAdjustmentRecord, ...]:
        wanted = set(reporting_revision_ids)
        return tuple(
            item
            for item in self._adjustments.values()
            if item.account_id == account_id and item.adjusts_reporting_revision_id in wanted
        )

    # -- consumer status -------------------------------------------------

    async def resolve_consumer_status_replay(
        self, status: ConsumerStatusRecord
    ) -> ConsumerStatusRecord | None:
        async with self._lock:
            return self._replay(status)

    def _replay(self, status: ConsumerStatusRecord) -> ConsumerStatusRecord | None:
        key = (status.account_id, status.consumer_id, status.reporting_status_id)
        existing = self._statuses.get(key)
        if existing is None:
            return None
        if self._status_identity[key] != _consumer_status_identity(status):
            raise LedgerConflictError(
                "STATUS_IDENTITY_CONFLICT",
                f"reporting_status_id {status.reporting_status_id} was already "
                "recorded with different content",
            )
        return existing

    async def record_consumer_status(
        self, status: ConsumerStatusRecord
    ) -> tuple[ConsumerStatusRecord, bool]:
        from adcp.reporting.evidence import consumer_reference

        consumer_reference(status.consumer_id)
        async with self._mutation():
            identity = _consumer_status_identity(status)
            existing = self._replay(status)
            if existing is not None:
                return existing, False
            from adcp.reporting.ledger.status_snapshot import (
                memory_snapshot,
                validate_status_evidence,
            )

            validate_status_evidence(status, memory_snapshot(self, status.account_id))
            leaf = self._current_status_leaf(status.chain_key)
            if status.supersedes_reporting_status_id:
                if (
                    leaf is None
                    or leaf.reporting_status_id != status.supersedes_reporting_status_id
                ):
                    raise LedgerConflictError(
                        "STATUS_SUPERSEDES_STALE",
                        "supersedes_reporting_status_id must name this chain's current leaf; "
                        "a stale pointer would let a successful retry erase a recorded outage",
                    )
                self._statuses[(leaf.account_id, leaf.consumer_id, leaf.reporting_status_id)] = (
                    replace(leaf, superseded=True)
                )
            elif leaf is not None:
                raise LedgerConflictError(
                    "STATUS_SUPERSEDES_REQUIRED",
                    "this chain already has a current statement; a new statement must "
                    "explicitly supersede it",
                )
            key = (status.account_id, status.consumer_id, status.reporting_status_id)
            self._statuses[key] = status
            self._status_identity[key] = identity
            self._append(
                status.account_id,
                "consumer_status",
                status.reporting_status_id,
                consumer_id=status.consumer_id,
            )
            from adcp.reporting.ledger.status_snapshot import settle_memory_snapshot

            settle_memory_snapshot(self, status.account_id)
            if self._notification_state is not None:
                self._dirty_status(
                    ReportingStatusScope(
                        status.account_id,
                        status.generation_key,
                        status.reporting_obligation_id,
                        status.consumer_id,
                    ),
                    "consumer_status",
                    (
                        ReportingStatusEvidence("consumer_status", leaf.reporting_status_id)
                        if leaf is not None
                        else None
                    ),
                    ReportingStatusEvidence(
                        "consumer_status",
                        status.reporting_status_id,
                        supersedes_id=status.supersedes_reporting_status_id,
                    ),
                )
            return status, True

    async def record_consumer_status_with_lifecycle(
        self, status: ConsumerStatusRecord
    ) -> tuple[ConsumerStatusRecord, bool]:
        return await self.record_consumer_status(status)

    async def read_status_snapshot(self, *, caller: ReportingCaller) -> ReportingStatusSnapshot:
        from adcp.reporting.ledger.status_snapshot import settle_memory_snapshot

        async with self._mutation():
            from adcp.reporting.ledger.delivery_models import ReportingDeliveryPrincipal
            from adcp.reporting.materializer.capture import private_snapshot

            return private_snapshot(
                settle_memory_snapshot(self, caller.account_id),
                ReportingDeliveryPrincipal(caller.account_id, caller.consumer_id),
            )

    def _current_status_leaf(self, chain_key: tuple[Any, ...]) -> ConsumerStatusRecord | None:
        for item in self._statuses.values():
            if item.chain_key == chain_key and not item.superseded:
                return item
        return None

    async def list_consumer_statuses(
        self,
        *,
        account_id: str,
        consumer_id: str,
        reporting_obligation_ids: Sequence[str] | None = None,
    ) -> tuple[ConsumerStatusRecord, ...]:
        wanted = set(reporting_obligation_ids) if reporting_obligation_ids is not None else None
        result = []
        for item in self._statuses.values():
            if item.account_id != account_id or item.consumer_id != consumer_id:
                continue
            if wanted is not None and not self._matches_obligations(item, wanted):
                continue
            result.append(item)
        return tuple(result)

    def _matches_obligations(self, status: ConsumerStatusRecord, wanted: set[str]) -> bool:
        """Attach a statement to an obligation by id *or* by logical period key.

        The logical-key path is what makes the loop repairable: a statement
        filed as ``obligation_missing`` predates the obligation it is about, and
        when the seller later creates that obligation the existing chain must
        attach to it rather than being lost, forked, or reset.
        """
        for obligation_id in wanted:
            obligation = self._obligations.get(obligation_id)
            if obligation is None or obligation.account_id != status.account_id:
                continue
            if status.reporting_obligation_id == obligation_id:
                return True
            if (
                obligation.generation_key == status.generation_key
                and obligation.report_definition_id == status.report_definition_id
                and _utc(obligation.period.start) == _utc(status.period_start)
                and _utc(obligation.period.end) == _utc(status.period_end)
            ):
                return True
        return False

    # -- issue lifecycle -------------------------------------------------

    async def ensure_issue_opened(
        self,
        *,
        issue_key: str,
        account_id: str,
        consumer_id: str | None,
        observed_at: datetime,
        status_scope: ReportingStatusScope | None = None,
    ) -> ReportingIssueLifecycle:
        async with self._mutation():
            key = (account_id, issue_key)
            live = self._issues.get(key)
            if live is not None:
                if live.consumer_id != consumer_id:
                    raise ReportingNotificationError("invalid_status_scope")
                previous_scope = self._issue_status_scopes.get((account_id, live.issue_id))
                if status_scope is not None:
                    validate_scope_refinement(previous_scope, status_scope)
                else:
                    status_scope = previous_scope
            if live is not None and live.live:
                self._dirty_issue(live, status_scope, live)
                return live
            generation = self._issue_generations.get(key, 0) + 1
            record = ReportingIssueLifecycle(
                issue_key=issue_key,
                issue_id=issue_id_for_occurrence(issue_key, generation),
                account_id=account_id,
                consumer_id=consumer_id,
                opened_at=_utc(observed_at),
                issue_state="open",
                generation=generation,
            )
            self._resolve_issue_scope(
                account_id=account_id,
                consumer_id=consumer_id,
                issue_id=record.issue_id,
                status_scope=status_scope,
            )
            self._issue_generations[key] = generation
            self._issues[key] = record
            self._dirty_issue(record, status_scope)
            return record

    async def set_issue_state(
        self,
        *,
        issue_key: str,
        account_id: str,
        state: Literal["acknowledged", "waived"],
        at: datetime,
        external_ref: str | None = None,
        status_scope: ReportingStatusScope | None = None,
    ) -> ReportingIssueLifecycle:
        async with self._mutation():
            if state not in {"acknowledged", "waived"}:
                raise LedgerConflictError(
                    "ISSUE_STATE_NOT_OPERATOR_SETTABLE",
                    f"issue_state {state!r} is not settable by an operator. 'resolved' is "
                    "reachable only when the condition actually clears -- the projection "
                    "retires it -- because a seller must not retire a mismatch out of a "
                    "degraded projection while the statement that caused it is still the "
                    "consumer's current leaf",
                )
            key = (account_id, issue_key)
            live = self._issues.get(key)
            if live is None or not live.live:
                raise LedgerConflictError(
                    "ISSUE_NOT_OPEN",
                    f"no open issue {issue_key!r} for this account; a retired issue cannot be "
                    "reopened, and a recurrence gets a new occurrence",
                )
            check_issue_state_transition(live.issue_state, state)
            if state == "waived" and live.issue_state != "waived":
                from adcp.reporting.ledger.status_projection import bind_mismatch_waiver
                from adcp.reporting.ledger.status_snapshot import memory_snapshot

                live = bind_mismatch_waiver(memory_snapshot(self, account_id), live)
            updated = replace(
                live,
                issue_state=state,
                external_ref=external_ref or live.external_ref,
                # Set on the way into a retired state and never cleared.
                retired_at=(
                    (
                        live.retired_at
                        if live.waived_reporting_status_id is not None
                        and live.retired_at is not None
                        else _utc(at)
                    )
                    if state == "waived"
                    else live.retired_at
                ),
            )
            self._resolve_issue_scope(
                account_id=account_id,
                consumer_id=live.consumer_id,
                issue_id=live.issue_id,
                status_scope=status_scope,
            )
            self._issues[key] = updated
            # Derive the no-op from the resulting record rather than predicting
            # it: an idempotent re-acknowledge changes nothing and enqueues
            # nothing, while anything that does move retained evidence stays
            # reconstructable for the projector.
            self._dirty_issue(updated, status_scope, live)
            return updated

    async def retire_issue(
        self,
        *,
        issue_key: str,
        account_id: str,
        at: datetime,
        status_scope: ReportingStatusScope | None = None,
    ) -> ReportingIssueLifecycle | None:
        async with self._mutation():
            key = (account_id, issue_key)
            live = self._issues.get(key)
            if live is None or not live.live or not issue_is_retirable(live.issue_state):
                # Convergent: nothing live, or already waived. A waived issue
                # is retired from the projection by agreement, and overwriting
                # that readable act with `resolved` is an edge the forward-only
                # lifecycle forbids -- enforced here rather than left to each
                # caller to remember.
                return None
            check_issue_state_transition(live.issue_state, "resolved")
            retired = replace(live, issue_state="resolved", retired_at=_utc(at))
            self._resolve_issue_scope(
                account_id=account_id,
                consumer_id=live.consumer_id,
                issue_id=live.issue_id,
                status_scope=status_scope,
            )
            self._issues[key] = retired
            self._dirty_issue(retired, status_scope, live)
            return retired

    async def get_issue(self, *, issue_key: str, account_id: str) -> ReportingIssueLifecycle | None:
        async with self._lock:
            # Live occurrences only, matching the Protocol docstring and the
            # Postgres store. Returning a retired row here would let the
            # projection re-emit a resolved issue's opened_at.
            live = self._issues.get((account_id, issue_key))
            return live if live is not None and live.live else None

    # -- snapshots -------------------------------------------------------

    async def open_snapshot(
        self, *, caller: ReportingCaller, filters_fingerprint: str
    ) -> LedgerSnapshot:
        account_id = caller.account_id
        async with self._lock:
            as_of = _utc(self._clock())
            max_sequence = max(
                (
                    sequence
                    for sequence, account, kind, record_id, _ in self._changes
                    if account == account_id and self._change_owners[sequence] == caller.consumer_id
                ),
                default=0,
            )
            return LedgerSnapshot(
                snapshot_id="rpls_"
                + _fingerprint([account_id, caller.consumer_id, filters_fingerprint, max_sequence])[
                    :32
                ],
                account_id=account_id,
                consumer_id=caller.consumer_id,
                ledger_as_of=as_of,
                max_sequence=max_sequence,
            )

    async def read_page(
        self,
        *,
        snapshot: LedgerSnapshot,
        consumer_id: str | None,
        delivery_config_ids: Sequence[str] | None,
        media_buy_ids: Sequence[str] | None,
        offset: int,
        limit: int,
        changes_after_sequence: int | None,
        feed_purposes: Sequence[str] | None = None,
        period_start: datetime | None = None,
        period_end: datetime | None = None,
    ) -> LedgerPage:
        if consumer_id not in {None, snapshot.consumer_id}:
            raise LedgerConflictError(
                "CURSOR_SNAPSHOT_MISMATCH", "snapshot belongs to another caller"
            )
        consumer_id = snapshot.consumer_id
        lower = changes_after_sequence or 0
        selected = [
            item
            for item in self._changes
            if item[1] == snapshot.account_id
            and self._change_owners[item[0]] == consumer_id
            and lower < item[0] <= snapshot.max_sequence
        ]
        config_filter = set(delivery_config_ids) if delivery_config_ids else None
        media_buy_filter = set(media_buy_ids) if media_buy_ids else None

        records: list[tuple[int, LedgerRecordKind, Any]] = []
        for sequence, _account, kind, record_id, _committed in sorted(selected):
            record = self._resolve(kind, record_id, snapshot.account_id, consumer_id)
            if record is None or record.account_id != snapshot.account_id:
                continue
            if not self._in_scope(
                kind,
                record,
                config_filter,
                media_buy_filter,
                consumer_id,
                feed_purposes,
                period_start,
                period_end,
            ):
                continue
            records.append((sequence, kind, record))

        window = records[offset : offset + limit]
        has_more = offset + limit < len(records)
        return LedgerPage(
            obligations=tuple(item[2] for item in window if item[1] == "obligation"),
            revisions=tuple(item[2] for item in window if item[1] == "revision"),
            adjustments=tuple(item[2] for item in window if item[1] == "adjustment"),
            consumer_statuses=tuple(item[2] for item in window if item[1] == "consumer_status"),
            total_count=len(records),
            has_more=has_more,
            cursor=(
                encode_cursor({"snapshot": snapshot.snapshot_id, "offset": offset + limit})
                if has_more
                else None
            ),
        )

    def _resolve(
        self, kind: str, record_id: str, account_id: str = "", consumer_id: str | None = None
    ) -> Any:
        if kind == "obligation":
            return self._obligations.get(record_id)
        if kind == "revision":
            return self._revisions.get(record_id)
        if kind == "adjustment":
            return self._adjustments.get(record_id)
        if kind == "consumer_status":
            return self._statuses.get((account_id, consumer_id or "", record_id))
        # Optional delivery records have their own retained snapshot reader.
        # Core status must not count or expose them, even in a shared ledger.
        return None

    def _in_scope(
        self,
        kind: LedgerRecordKind,
        record: Any,
        config_filter: set[str] | None,
        media_buy_filter: set[str] | None,
        consumer_id: str | None,
        feed_purposes: Sequence[str] | None = None,
        period_start: datetime | None = None,
        period_end: datetime | None = None,
    ) -> bool:
        from adcp.reporting.ledger.status_projection import configuration_selected, period_selected

        if kind == "consumer_status":
            # A caller sees only its own statements; another consumer's
            # operational status is not disclosed.
            if consumer_id is None or record.consumer_id != consumer_id:
                return False
            configuration = self._configurations.get(record.generation_key)
            return (
                configuration is not None
                and configuration_selected(
                    configuration,
                    delivery_config_ids=tuple(config_filter or ()),
                    media_buy_ids=tuple(media_buy_filter or ()),
                    feed_purposes=feed_purposes or (),
                )
                and period_selected(
                    record.period_start, record.period_end, period_start, period_end
                )
            )
        obligation = self._obligation_for(kind, record)
        if obligation is None or obligation.consumer_id != consumer_id:
            return False
        configuration = self._configurations.get(obligation.generation_key)
        if (
            configuration is None
            or not configuration_selected(
                configuration,
                delivery_config_ids=tuple(config_filter or ()),
                media_buy_ids=tuple(media_buy_filter or ()),
                feed_purposes=feed_purposes or (),
            )
            or not period_selected(
                obligation.period.start, obligation.period.end, period_start, period_end
            )
        ):
            return False
        if config_filter is not None and obligation.delivery_config_id not in config_filter:
            return False
        if media_buy_filter is not None and not media_buy_filter.intersection(
            obligation.media_buy_ids
        ):
            return False
        return True

    def _obligation_for(
        self, kind: LedgerRecordKind, record: Any
    ) -> ReportingObligationRecord | None:
        if kind == "obligation":
            found: ReportingObligationRecord = record
            return found
        if kind == "revision":
            return self._obligations.get(record.reporting_obligation_id)
        revision = self._revisions.get(record.adjusts_reporting_revision_id)
        return self._obligations.get(revision.reporting_obligation_id) if revision else None

    # -- leasing ---------------------------------------------------------

    def _configuration_lease_eligible(self, configuration: ReportingConfiguration) -> bool:
        return not configuration.quarantined

    async def lease_period_close(
        self, *, worker_id: str, now: datetime, lease_seconds: float
    ) -> LeasedConfiguration | None:
        from datetime import timedelta

        async with self._lock:
            moment = _utc(now)
            # Rank leasable generations exactly the way the SQL store's
            # `ORDER BY lease_turn, lease_expires_at NULLS FIRST, ...` does:
            # whichever generation went longest without a turn goes first.
            #
            # The turn has to be the *primary* term. The caller's `now` has
            # already excluded every live lease, so among the survivors the
            # expiry carries no fairness information -- and preferring unheld
            # over expired ahead of the turn starves a crashed generation
            # forever: a peer that is leased and released every turn is always
            # unheld, so it wins every comparison while the generation whose
            # worker died stays expired and never closes another period.
            # Expiry and the generation key only break exact turn ties, so the
            # order stays total and never depends on physical layout.
            ranked: list[
                tuple[
                    tuple[int, int, float, str, str, str, int],
                    ReportingConfigurationGenerationKey,
                ]
            ] = []
            for key in self._configurations:
                if not self._configuration_lease_eligible(self._configurations[key]):
                    continue
                turn = self._lease_turns.get(key, 0)
                held = self._leases.get(key)
                tail = (
                    key.account_id,
                    key.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                )
                if held is None:
                    ranked.append(((turn, 0, 0.0, *tail), key))
                elif _utc(held[1]) <= moment:
                    ranked.append(((turn, 1, _utc(held[1]).timestamp(), *tail), key))
            if not ranked:
                return None
            # The generation key is part of the rank, so the order is total and
            # both stores make the same choice. Ranking by `min` alone would
            # fall back to whichever generation this process happened to accept
            # first, which the SQL store cannot reproduce and no adopter can
            # observe consistently across a restart or a second worker.
            key = min(ranked, key=lambda item: item[0])[1]
            configuration = self._configurations[key]
            expires = moment + timedelta(seconds=lease_seconds)
            self._lease_turn += 1
            self._lease_turns[key] = self._lease_turn
            self._leases[key] = (worker_id, expires)
            return LeasedConfiguration(
                account_id=configuration.account_id,
                consumer_id=configuration.consumer_id,
                delivery_config_id=configuration.delivery_config_id,
                delivery_config_version=configuration.delivery_config_version,
                lease_expires_at=expires,
            )

    async def release_period_close(self, lease: LeasedConfiguration, *, worker_id: str) -> None:
        async with self._lock:
            key = lease.generation_key
            held = self._leases.get(key)
            if held == (worker_id, _utc(lease.lease_expires_at)):
                del self._leases[key]

Process-local reference store. Correct, ordered, and not durable.

clock supplies change timestamps and the snapshot observation boundary, standing in for the database clock a durable store reads. Override it to place a test's ledger boundary at a deliberate instant.

Subclasses

Methods

async def commit_adjustment(self,
adjustment: ReportingAdjustmentRecord) ‑> ReportingAdjustmentRecord
Expand source code
async def commit_adjustment(
    self, adjustment: ReportingAdjustmentRecord
) -> ReportingAdjustmentRecord:
    async with self._mutation():
        existing = self._adjustments.get(adjustment.reporting_adjustment_id)
        if existing is not None:
            if existing.account_id != adjustment.account_id:
                raise LedgerConflictError("ADJUSTMENT_UNAVAILABLE", "adjustment is unavailable")
            if existing != adjustment:
                raise LedgerConflictError(
                    "ADJUSTMENT_IMMUTABLE", "adjustment content is immutable"
                )
            return existing
        revision = self._revisions.get(adjustment.adjusts_reporting_revision_id)
        if revision is None or revision.account_id != adjustment.account_id:
            raise LedgerConflictError(
                "REVISION_NOT_FOUND", "an adjustment must name a committed revision"
            )
        if revision.finality != "official":
            raise LedgerConflictError(
                "ADJUSTMENT_REQUIRES_OFFICIAL",
                "adjustments correct an official revision; restate a snapshot with a "
                "superseding snapshot revision instead",
            )
        obligation = self._obligations[revision.reporting_obligation_id]
        if self._configurations[obligation.generation_key].quarantined:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED", "legacy evidence is read-only"
            )
        validate_adjustment_currency(
            self._obligations[revision.reporting_obligation_id], adjustment
        )
        self._adjustments[adjustment.reporting_adjustment_id] = adjustment
        self._append(
            adjustment.account_id,
            "adjustment",
            adjustment.reporting_adjustment_id,
            consumer_id=obligation.consumer_id,
        )
        if self._notification_state is not None:
            from adcp.reporting.ledger.notification_events import adjustment_event

            self._record_notification(
                adjustment_event(
                    adjustment,
                    self._clock(),
                    consumer_id=self._obligations[revision.reporting_obligation_id].consumer_id,
                )
            )
            self._dirty_status(
                ReportingStatusScope.for_obligation(
                    self._obligations[revision.reporting_obligation_id]
                ),
                "adjustment",
                after=ReportingStatusEvidence("adjustment", adjustment.reporting_adjustment_id),
            )
        return adjustment
async def commit_obligation(self,
obligation: ReportingObligationRecord) ‑> ReportingObligationRecord
Expand source code
async def commit_obligation(
    self, obligation: ReportingObligationRecord
) -> ReportingObligationRecord:
    async with self._mutation():
        key = (
            obligation.generation_key,
            _utc(obligation.period.start).isoformat(),
            _utc(obligation.period.end).isoformat(),
        )
        existing_id = self._obligation_by_period.get(key)
        if existing_id is not None:
            return self._obligations[existing_id]
        if obligation.reporting_obligation_id in self._obligations:
            raise LedgerConflictError(
                "OBLIGATION_IDENTITY_CONFLICT",
                "the obligation identifier already belongs to a different logical period",
            )
        if obligation.generation_key not in self._configurations:
            raise LedgerConflictError(
                "UNKNOWN_CONFIGURATION_GENERATION", "generation is unavailable"
            )
        if self._configurations[obligation.generation_key].quarantined:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED",
                "establish a new owned generation after operator reconciliation",
            )
        require_frozen_currency(obligation.currency)
        self._obligations[obligation.reporting_obligation_id] = obligation
        self._obligation_by_period[key] = obligation.reporting_obligation_id
        self._append(
            obligation.account_id,
            "obligation",
            obligation.reporting_obligation_id,
            consumer_id=obligation.consumer_id,
        )
        if self._notification_state is not None:
            self._dirty_status(
                ReportingStatusScope.for_obligation(obligation),
                "obligation",
                after=ReportingStatusEvidence("obligation", obligation.reporting_obligation_id),
            )
        return obligation
async def commit_provisional_observation(self,
observation: ProvisionalObservation,
revision: ReportingRevisionRecord,
rows: Sequence[dict[str, Any]]) ‑> ReportingRevisionRecord
Expand source code
async def commit_provisional_observation(
    self,
    observation: ProvisionalObservation,
    revision: ReportingRevisionRecord,
    rows: Sequence[dict[str, Any]],
) -> ReportingRevisionRecord:
    acquisition = observation.acquisition
    key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
    if (
        revision.account_id != acquisition.account_id
        or revision.reporting_obligation_id != acquisition.obligation_id
        or revision.reporting_revision_id != observation.revision_id
    ):
        raise LedgerConflictError("OBSERVATION_CONFLICT", "observation identity differs")
    async with self._mutation():
        existing = self._provisional_observations.get(key)
        if existing is not None:
            if (
                existing.acquisition != acquisition
                or existing.revision_id != observation.revision_id
            ):
                raise LedgerConflictError("OBSERVATION_CONFLICT", "observation replay differs")
            if existing.revision_id not in self._revisions:
                raise LedgerConflictError(
                    "HISTORY_UNAVAILABLE", "observation revision is missing"
                )
            # A matching observation identity does not make conflicting
            # publication content an idempotent replay.
            return await self.commit_revision(revision, rows)
        retained = self._provisional_acquisitions.get(key)
        if retained != acquisition:
            raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition was not reserved")
        checkpoint = self._restatement_checkpoints.get(acquisition.obligation_id)
        if checkpoint is not None and checkpoint.next_observation != acquisition.ordinal:
            raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
        committed = await self.commit_revision(revision, rows)
        await self.record_restatement_checkpoint(
            RestatementCheckpoint(
                acquisition.account_id,
                acquisition.obligation_id,
                observation.checked_at,
                acquisition.ordinal + 1,
                observation.provisional_until,
            )
        )
        self._provisional_observations[key] = observation
        return committed
async def commit_revision(self,
revision: ReportingRevisionRecord,
rows: Sequence[dict[str, Any]]) ‑> ReportingRevisionRecord
Expand source code
async def commit_revision(
    self, revision: ReportingRevisionRecord, rows: Sequence[dict[str, Any]]
) -> ReportingRevisionRecord:
    rows = tuple(deepcopy(row) for row in rows)
    # Checked first, exactly as PgReportingLedgerStore does: a caller whose
    # rows do not match its own declared count must get the same code from
    # both stores, not whichever invariant that store happens to reach.
    if revision.row_count != len(rows):
        raise LedgerConflictError(
            "ROW_COUNT_MISMATCH",
            f"revision declares {revision.row_count} rows but {len(rows)} were supplied",
        )
    validate_managed_revision_rows(revision, rows)
    async with self._mutation():
        identity = _revision_identity(revision)
        existing = self._revisions.get(revision.reporting_revision_id)
        if existing is not None:
            if existing.account_id != revision.account_id:
                raise LedgerConflictError(
                    "REVISION_NOT_FOUND", "no such revision for this account"
                )
            if self._revision_identity[revision.reporting_revision_id] != identity:
                raise LedgerConflictError(
                    "REVISION_IMMUTABLE",
                    f"revision {revision.reporting_revision_id} already exists with "
                    "different content; a restatement is a new revision",
                )
            return existing
        obligation = self._obligations.get(revision.reporting_obligation_id)
        if obligation is None or obligation.account_id != revision.account_id:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND",
                "a revision must attach to an obligation committed at the period close",
            )
        if self._configurations[obligation.generation_key].quarantined:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED", "legacy evidence is read-only"
            )
        validate_revision_currency(obligation, revision, rows)
        siblings = [
            item
            for item in self._revisions.values()
            if item.reporting_obligation_id == revision.reporting_obligation_id
        ]
        if revision.finality == "official" and any(
            item.finality == "official" for item in siblings
        ):
            raise LedgerConflictError(
                "OFFICIAL_REVISION_TERMINAL",
                "an official revision already exists for this obligation; publish a later "
                "source correction as an adjustment",
            )
        if revision.supersedes_reporting_revision_id:
            self._require_current_leaf(revision, siblings)
        self._revisions[revision.reporting_revision_id] = revision
        self._revision_identity[revision.reporting_revision_id] = identity
        self._rows[revision.reporting_revision_id] = tuple(dict(row) for row in rows)
        self._append(
            revision.account_id,
            "revision",
            revision.reporting_revision_id,
            consumer_id=obligation.consumer_id,
        )
        if self._notification_state is not None:
            from adcp.reporting.ledger.notification_events import revision_event

            self._record_notification(
                revision_event(revision, self._clock(), consumer_id=obligation.consumer_id)
            )
            self._dirty_status(
                ReportingStatusScope.for_obligation(obligation),
                "revision",
                after=ReportingStatusEvidence(
                    "revision",
                    revision.reporting_revision_id,
                    readable=revision.readable,
                    supersedes_id=revision.supersedes_reporting_revision_id,
                ),
            )
        return revision
async def create_schema(self) ‑> None
Expand source code
async def create_schema(self) -> None:
    return None
async def ensure_issue_opened(self,
*,
issue_key: str,
account_id: str,
consumer_id: str | None,
observed_at: datetime,
status_scope: ReportingStatusScope | None = None) ‑> ReportingIssueLifecycle
Expand source code
async def ensure_issue_opened(
    self,
    *,
    issue_key: str,
    account_id: str,
    consumer_id: str | None,
    observed_at: datetime,
    status_scope: ReportingStatusScope | None = None,
) -> ReportingIssueLifecycle:
    async with self._mutation():
        key = (account_id, issue_key)
        live = self._issues.get(key)
        if live is not None:
            if live.consumer_id != consumer_id:
                raise ReportingNotificationError("invalid_status_scope")
            previous_scope = self._issue_status_scopes.get((account_id, live.issue_id))
            if status_scope is not None:
                validate_scope_refinement(previous_scope, status_scope)
            else:
                status_scope = previous_scope
        if live is not None and live.live:
            self._dirty_issue(live, status_scope, live)
            return live
        generation = self._issue_generations.get(key, 0) + 1
        record = ReportingIssueLifecycle(
            issue_key=issue_key,
            issue_id=issue_id_for_occurrence(issue_key, generation),
            account_id=account_id,
            consumer_id=consumer_id,
            opened_at=_utc(observed_at),
            issue_state="open",
            generation=generation,
        )
        self._resolve_issue_scope(
            account_id=account_id,
            consumer_id=consumer_id,
            issue_id=record.issue_id,
            status_scope=status_scope,
        )
        self._issue_generations[key] = generation
        self._issues[key] = record
        self._dirty_issue(record, status_scope)
        return record
async def find_obligation(self,
*,
account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
period_start: datetime,
period_end: datetime) ‑> ReportingObligationRecord | None
Expand source code
async def find_obligation(
    self,
    *,
    account_id: str,
    consumer_id: str,
    delivery_config_id: str,
    delivery_config_version: int,
    period_start: datetime,
    period_end: datetime,
) -> ReportingObligationRecord | None:
    key = (
        ReportingConfigurationGenerationKey(
            account_id=account_id,
            consumer_id=consumer_id,
            delivery_config_id=delivery_config_id,
            delivery_config_version=delivery_config_version,
        ),
        _utc(period_start).isoformat(),
        _utc(period_end).isoformat(),
    )
    found = self._obligation_by_period.get(key)
    return (
        await self.get_obligation(account_id=account_id, reporting_obligation_id=found)
        if found
        else None
    )
async def get_issue(self, *, issue_key: str, account_id: str) ‑> ReportingIssueLifecycle | None
Expand source code
async def get_issue(self, *, issue_key: str, account_id: str) -> ReportingIssueLifecycle | None:
    async with self._lock:
        # Live occurrences only, matching the Protocol docstring and the
        # Postgres store. Returning a retired row here would let the
        # projection re-emit a resolved issue's opened_at.
        live = self._issues.get((account_id, issue_key))
        return live if live is not None and live.live else None
async def get_obligation(self, *, account_id: str, reporting_obligation_id: str) ‑> ReportingObligationRecord | None
Expand source code
async def get_obligation(
    self, *, account_id: str, reporting_obligation_id: str
) -> ReportingObligationRecord | None:
    found = self._obligations.get(reporting_obligation_id)
    return found if found and found.account_id == account_id else None
async def get_provisional_acquisition(self, *, account_id: str, reporting_obligation_id: str, ordinal: int) ‑> ProvisionalAcquisition | None
Expand source code
async def get_provisional_acquisition(
    self, *, account_id: str, reporting_obligation_id: str, ordinal: int
) -> ProvisionalAcquisition | None:
    return self._provisional_acquisitions.get((account_id, reporting_obligation_id, ordinal))
async def get_provisional_observation(self, *, account_id: str, reporting_obligation_id: str) ‑> ProvisionalObservation | None
Expand source code
async def get_provisional_observation(
    self, *, account_id: str, reporting_obligation_id: str
) -> ProvisionalObservation | None:
    observations = [
        value
        for (account, obligation, _), value in self._provisional_observations.items()
        if account == account_id and obligation == reporting_obligation_id
    ]
    return max(observations, key=lambda item: item.acquisition.ordinal, default=None)
async def get_restatement_checkpoint(self, *, account_id: str, reporting_obligation_id: str) ‑> RestatementCheckpoint | None
Expand source code
async def get_restatement_checkpoint(
    self, *, account_id: str, reporting_obligation_id: str
) -> RestatementCheckpoint | None:
    checkpoint = self._restatement_checkpoints.get(reporting_obligation_id)
    return checkpoint if checkpoint and checkpoint.account_id == account_id else None
async def get_retry_schedule(self, *, scope_key: str) ‑> RetryScheduleEntry | None
Expand source code
async def get_retry_schedule(self, *, scope_key: str) -> RetryScheduleEntry | None:
    return self._retry_schedules.get(scope_key)
async def get_revision(self, *, account_id: str, reporting_revision_id: str) ‑> ReportingRevisionRecord | None
Expand source code
async def get_revision(
    self, *, account_id: str, reporting_revision_id: str
) -> ReportingRevisionRecord | None:
    found = self._revisions.get(reporting_revision_id)
    return found if found and found.account_id == account_id else None
async def lease_period_close(self, *, worker_id: str, now: datetime, lease_seconds: float) ‑> LeasedConfiguration | None
Expand source code
async def lease_period_close(
    self, *, worker_id: str, now: datetime, lease_seconds: float
) -> LeasedConfiguration | None:
    from datetime import timedelta

    async with self._lock:
        moment = _utc(now)
        # Rank leasable generations exactly the way the SQL store's
        # `ORDER BY lease_turn, lease_expires_at NULLS FIRST, ...` does:
        # whichever generation went longest without a turn goes first.
        #
        # The turn has to be the *primary* term. The caller's `now` has
        # already excluded every live lease, so among the survivors the
        # expiry carries no fairness information -- and preferring unheld
        # over expired ahead of the turn starves a crashed generation
        # forever: a peer that is leased and released every turn is always
        # unheld, so it wins every comparison while the generation whose
        # worker died stays expired and never closes another period.
        # Expiry and the generation key only break exact turn ties, so the
        # order stays total and never depends on physical layout.
        ranked: list[
            tuple[
                tuple[int, int, float, str, str, str, int],
                ReportingConfigurationGenerationKey,
            ]
        ] = []
        for key in self._configurations:
            if not self._configuration_lease_eligible(self._configurations[key]):
                continue
            turn = self._lease_turns.get(key, 0)
            held = self._leases.get(key)
            tail = (
                key.account_id,
                key.consumer_id,
                key.delivery_config_id,
                key.delivery_config_version,
            )
            if held is None:
                ranked.append(((turn, 0, 0.0, *tail), key))
            elif _utc(held[1]) <= moment:
                ranked.append(((turn, 1, _utc(held[1]).timestamp(), *tail), key))
        if not ranked:
            return None
        # The generation key is part of the rank, so the order is total and
        # both stores make the same choice. Ranking by `min` alone would
        # fall back to whichever generation this process happened to accept
        # first, which the SQL store cannot reproduce and no adopter can
        # observe consistently across a restart or a second worker.
        key = min(ranked, key=lambda item: item[0])[1]
        configuration = self._configurations[key]
        expires = moment + timedelta(seconds=lease_seconds)
        self._lease_turn += 1
        self._lease_turns[key] = self._lease_turn
        self._leases[key] = (worker_id, expires)
        return LeasedConfiguration(
            account_id=configuration.account_id,
            consumer_id=configuration.consumer_id,
            delivery_config_id=configuration.delivery_config_id,
            delivery_config_version=configuration.delivery_config_version,
            lease_expires_at=expires,
        )
async def list_adjustments(self, *, account_id: str, reporting_revision_ids: Sequence[str]) ‑> tuple[ReportingAdjustmentRecord, ...]
Expand source code
async def list_adjustments(
    self, *, account_id: str, reporting_revision_ids: Sequence[str]
) -> tuple[ReportingAdjustmentRecord, ...]:
    wanted = set(reporting_revision_ids)
    return tuple(
        item
        for item in self._adjustments.values()
        if item.account_id == account_id and item.adjusts_reporting_revision_id in wanted
    )
async def list_all_configurations(self) ‑> tuple[ReportingConfiguration, ...]
Expand source code
async def list_all_configurations(self) -> tuple[ReportingConfiguration, ...]:
    return tuple(
        sorted(
            (c for c in self._configurations.values() if not c.quarantined),
            key=lambda item: (
                item.account_id,
                item.consumer_id,
                item.delivery_config_id,
                item.delivery_config_version,
            ),
        )
    )
async def list_configurations(self,
*,
caller: ReportingCaller,
delivery_config_ids: Sequence[str] | None = None) ‑> tuple[ReportingConfiguration, ...]
Expand source code
async def list_configurations(
    self, *, caller: ReportingCaller, delivery_config_ids: Sequence[str] | None = None
) -> tuple[ReportingConfiguration, ...]:
    wanted = set(delivery_config_ids) if delivery_config_ids else None
    return tuple(
        configuration
        for configuration in self._configurations.values()
        if configuration.account_id == caller.account_id
        and configuration.consumer_id == caller.consumer_id
        and (wanted is None or configuration.delivery_config_id in wanted)
    )
async def list_consumer_statuses(self,
*,
account_id: str,
consumer_id: str,
reporting_obligation_ids: Sequence[str] | None = None) ‑> tuple[ConsumerStatusRecord, ...]
Expand source code
async def list_consumer_statuses(
    self,
    *,
    account_id: str,
    consumer_id: str,
    reporting_obligation_ids: Sequence[str] | None = None,
) -> tuple[ConsumerStatusRecord, ...]:
    wanted = set(reporting_obligation_ids) if reporting_obligation_ids is not None else None
    result = []
    for item in self._statuses.values():
        if item.account_id != account_id or item.consumer_id != consumer_id:
            continue
        if wanted is not None and not self._matches_obligations(item, wanted):
            continue
        result.append(item)
    return tuple(result)
async def list_revisions(self, *, account_id: str, reporting_obligation_id: str) ‑> tuple[ReportingRevisionRecord, ...]
Expand source code
async def list_revisions(
    self, *, account_id: str, reporting_obligation_id: str
) -> tuple[ReportingRevisionRecord, ...]:
    return tuple(
        item
        for item in self._revisions.values()
        if item.account_id == account_id
        and item.reporting_obligation_id == reporting_obligation_id
    )
async def open_snapshot(self,
*,
caller: ReportingCaller,
filters_fingerprint: str) ‑> LedgerSnapshot
Expand source code
async def open_snapshot(
    self, *, caller: ReportingCaller, filters_fingerprint: str
) -> LedgerSnapshot:
    account_id = caller.account_id
    async with self._lock:
        as_of = _utc(self._clock())
        max_sequence = max(
            (
                sequence
                for sequence, account, kind, record_id, _ in self._changes
                if account == account_id and self._change_owners[sequence] == caller.consumer_id
            ),
            default=0,
        )
        return LedgerSnapshot(
            snapshot_id="rpls_"
            + _fingerprint([account_id, caller.consumer_id, filters_fingerprint, max_sequence])[
                :32
            ],
            account_id=account_id,
            consumer_id=caller.consumer_id,
            ledger_as_of=as_of,
            max_sequence=max_sequence,
        )
async def put_configuration(self,
configuration: ReportingConfiguration) ‑> None
Expand source code
async def put_configuration(self, configuration: ReportingConfiguration) -> None:
    reject_reserved_authoritative_party(configuration)
    async with self._mutation():
        key = configuration.generation_key
        existing = self._configurations.get(key)
        if existing is not None and existing.quarantined:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED", "operator reconciliation is required"
            )
        if existing is not None and _fingerprint(_config_payload(existing)) != _fingerprint(
            _config_payload(configuration)
        ):
            raise LedgerConflictError(
                "CONFIGURATION_GENERATION_IMMUTABLE",
                f"configuration {key.delivery_config_id}@{key.delivery_config_version} "
                "already exists with different content for this account; "
                "publish a new version instead of editing a retained generation",
            )
        self._configurations[key] = configuration
        changed = existing is None or configuration_lifecycle(
            existing
        ) != configuration_lifecycle(configuration)
        if changed and self._notification_state is not None:
            self._dirty_status(
                ReportingStatusScope(configuration.account_id, configuration.generation_key),
                "configuration",
                before=configuration_evidence(existing) if existing is not None else None,
                after=configuration_evidence(configuration),
            )
async def read_page(self,
*,
snapshot: LedgerSnapshot,
consumer_id: str | None,
delivery_config_ids: Sequence[str] | None,
media_buy_ids: Sequence[str] | None,
offset: int,
limit: int,
changes_after_sequence: int | None,
feed_purposes: Sequence[str] | None = None,
period_start: datetime | None = None,
period_end: datetime | None = None) ‑> LedgerPage
Expand source code
async def read_page(
    self,
    *,
    snapshot: LedgerSnapshot,
    consumer_id: str | None,
    delivery_config_ids: Sequence[str] | None,
    media_buy_ids: Sequence[str] | None,
    offset: int,
    limit: int,
    changes_after_sequence: int | None,
    feed_purposes: Sequence[str] | None = None,
    period_start: datetime | None = None,
    period_end: datetime | None = None,
) -> LedgerPage:
    if consumer_id not in {None, snapshot.consumer_id}:
        raise LedgerConflictError(
            "CURSOR_SNAPSHOT_MISMATCH", "snapshot belongs to another caller"
        )
    consumer_id = snapshot.consumer_id
    lower = changes_after_sequence or 0
    selected = [
        item
        for item in self._changes
        if item[1] == snapshot.account_id
        and self._change_owners[item[0]] == consumer_id
        and lower < item[0] <= snapshot.max_sequence
    ]
    config_filter = set(delivery_config_ids) if delivery_config_ids else None
    media_buy_filter = set(media_buy_ids) if media_buy_ids else None

    records: list[tuple[int, LedgerRecordKind, Any]] = []
    for sequence, _account, kind, record_id, _committed in sorted(selected):
        record = self._resolve(kind, record_id, snapshot.account_id, consumer_id)
        if record is None or record.account_id != snapshot.account_id:
            continue
        if not self._in_scope(
            kind,
            record,
            config_filter,
            media_buy_filter,
            consumer_id,
            feed_purposes,
            period_start,
            period_end,
        ):
            continue
        records.append((sequence, kind, record))

    window = records[offset : offset + limit]
    has_more = offset + limit < len(records)
    return LedgerPage(
        obligations=tuple(item[2] for item in window if item[1] == "obligation"),
        revisions=tuple(item[2] for item in window if item[1] == "revision"),
        adjustments=tuple(item[2] for item in window if item[1] == "adjustment"),
        consumer_statuses=tuple(item[2] for item in window if item[1] == "consumer_status"),
        total_count=len(records),
        has_more=has_more,
        cursor=(
            encode_cursor({"snapshot": snapshot.snapshot_id, "offset": offset + limit})
            if has_more
            else None
        ),
    )
async def read_revision_rows(self,
*,
account_id: str,
reporting_revision_id: str,
cursor: str | None = None,
limit: int = 500) ‑> ReportingRowPage
Expand source code
async def read_revision_rows(
    self,
    *,
    account_id: str,
    reporting_revision_id: str,
    cursor: str | None = None,
    limit: int = 500,
) -> ReportingRowPage:
    revision = await self.get_revision(
        account_id=account_id, reporting_revision_id=reporting_revision_id
    )
    if revision is None:
        raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
    rows = self._rows.get(reporting_revision_id, ())
    offset = revision_row_offset(cursor, reporting_revision_id, limit)
    window = rows[offset : offset + limit]
    has_more = offset + limit < len(rows)
    return ReportingRowPage(
        reporting_revision_id=reporting_revision_id,
        rows=tuple(deepcopy(row) for row in window),
        total_count=len(rows),
        has_more=has_more,
        cursor=(
            encode_cursor(
                {"ownership": 2, "revision": reporting_revision_id, "offset": offset + limit}
            )
            if has_more
            else None
        ),
    )
async def read_status_snapshot(self,
*,
caller: ReportingCaller) ‑> ReportingStatusSnapshot
Expand source code
async def read_status_snapshot(self, *, caller: ReportingCaller) -> ReportingStatusSnapshot:
    from adcp.reporting.ledger.status_snapshot import settle_memory_snapshot

    async with self._mutation():
        from adcp.reporting.ledger.delivery_models import ReportingDeliveryPrincipal
        from adcp.reporting.materializer.capture import private_snapshot

        return private_snapshot(
            settle_memory_snapshot(self, caller.account_id),
            ReportingDeliveryPrincipal(caller.account_id, caller.consumer_id),
        )
async def record_consumer_status(self,
status: ConsumerStatusRecord) ‑> tuple[ConsumerStatusRecord, bool]
Expand source code
async def record_consumer_status(
    self, status: ConsumerStatusRecord
) -> tuple[ConsumerStatusRecord, bool]:
    from adcp.reporting.evidence import consumer_reference

    consumer_reference(status.consumer_id)
    async with self._mutation():
        identity = _consumer_status_identity(status)
        existing = self._replay(status)
        if existing is not None:
            return existing, False
        from adcp.reporting.ledger.status_snapshot import (
            memory_snapshot,
            validate_status_evidence,
        )

        validate_status_evidence(status, memory_snapshot(self, status.account_id))
        leaf = self._current_status_leaf(status.chain_key)
        if status.supersedes_reporting_status_id:
            if (
                leaf is None
                or leaf.reporting_status_id != status.supersedes_reporting_status_id
            ):
                raise LedgerConflictError(
                    "STATUS_SUPERSEDES_STALE",
                    "supersedes_reporting_status_id must name this chain's current leaf; "
                    "a stale pointer would let a successful retry erase a recorded outage",
                )
            self._statuses[(leaf.account_id, leaf.consumer_id, leaf.reporting_status_id)] = (
                replace(leaf, superseded=True)
            )
        elif leaf is not None:
            raise LedgerConflictError(
                "STATUS_SUPERSEDES_REQUIRED",
                "this chain already has a current statement; a new statement must "
                "explicitly supersede it",
            )
        key = (status.account_id, status.consumer_id, status.reporting_status_id)
        self._statuses[key] = status
        self._status_identity[key] = identity
        self._append(
            status.account_id,
            "consumer_status",
            status.reporting_status_id,
            consumer_id=status.consumer_id,
        )
        from adcp.reporting.ledger.status_snapshot import settle_memory_snapshot

        settle_memory_snapshot(self, status.account_id)
        if self._notification_state is not None:
            self._dirty_status(
                ReportingStatusScope(
                    status.account_id,
                    status.generation_key,
                    status.reporting_obligation_id,
                    status.consumer_id,
                ),
                "consumer_status",
                (
                    ReportingStatusEvidence("consumer_status", leaf.reporting_status_id)
                    if leaf is not None
                    else None
                ),
                ReportingStatusEvidence(
                    "consumer_status",
                    status.reporting_status_id,
                    supersedes_id=status.supersedes_reporting_status_id,
                ),
            )
        return status, True
async def record_consumer_status_with_lifecycle(self,
status: ConsumerStatusRecord) ‑> tuple[ConsumerStatusRecord, bool]
Expand source code
async def record_consumer_status_with_lifecycle(
    self, status: ConsumerStatusRecord
) -> tuple[ConsumerStatusRecord, bool]:
    return await self.record_consumer_status(status)
async def record_restatement_checkpoint(self,
checkpoint: RestatementCheckpoint) ‑> RestatementCheckpoint
Expand source code
async def record_restatement_checkpoint(
    self, checkpoint: RestatementCheckpoint
) -> RestatementCheckpoint:
    async with self._mutation():
        obligation = self._obligations.get(checkpoint.reporting_obligation_id)
        if obligation is None or obligation.account_id != checkpoint.account_id:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND",
                "a restatement checkpoint must attach to an obligation for this account",
            )
        existing = self._restatement_checkpoints.get(checkpoint.reporting_obligation_id)
        if existing is not None:
            if checkpoint.next_observation < existing.next_observation:
                return existing
            if checkpoint.next_observation == existing.next_observation and _utc(
                checkpoint.checked_at
            ) <= _utc(existing.checked_at):
                return existing
        self._restatement_checkpoints[checkpoint.reporting_obligation_id] = checkpoint
        return checkpoint
async def record_retry_schedule(self,
entry: RetryScheduleEntry) ‑> RetryScheduleEntry
Expand source code
async def record_retry_schedule(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
    async with self._mutation():
        existing = self._retry_schedules.get(entry.scope_key)
        if existing is not None:
            assert entry.recorded_at is not None and existing.recorded_at is not None
            if _utc(entry.recorded_at) < _utc(existing.recorded_at):
                return existing
            if entry.attempt > 0 and not entry.blocked:
                if existing.blocked and not entry.replayed:
                    return existing
                if entry.attempt < existing.attempt and not entry.replayed:
                    return existing
                if not entry.replayed and not existing.blocked:
                    entry = replace(
                        entry,
                        retry_not_before=max(
                            entry.retry_not_before, existing.retry_not_before, key=_utc
                        ),
                    )
        stored = replace(entry, replayed=False)
        self._retry_schedules[entry.scope_key] = stored
        return stored
async def release_period_close(self,
lease: LeasedConfiguration,
*,
worker_id: str) ‑> None
Expand source code
async def release_period_close(self, lease: LeasedConfiguration, *, worker_id: str) -> None:
    async with self._lock:
        key = lease.generation_key
        held = self._leases.get(key)
        if held == (worker_id, _utc(lease.lease_expires_at)):
            del self._leases[key]
async def reserve_provisional_acquisition(self, acquisition: ProvisionalAcquisition) ‑> ProvisionalAcquisition
Expand source code
async def reserve_provisional_acquisition(
    self, acquisition: ProvisionalAcquisition
) -> ProvisionalAcquisition:
    # Canonical round-trip also detaches all request collections.
    acquisition = ProvisionalAcquisition.from_wire(acquisition.to_wire())
    key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
    async with self._mutation():
        existing = self._provisional_acquisitions.get(key)
        if existing is not None:
            return existing
        obligation = self._obligations.get(acquisition.obligation_id)
        if obligation is None or obligation.account_id != acquisition.account_id:
            raise LedgerConflictError("OBLIGATION_NOT_FOUND", "unknown observation obligation")
        if not acquisition.binds(obligation):
            raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition generation differs")
        if any(
            item.account_id == acquisition.account_id
            and item.execution_key == acquisition.execution_key
            for item in self._provisional_acquisitions.values()
        ):
            raise LedgerConflictError(
                "OBSERVATION_CONFLICT", "execution key is already reserved"
            )
        checkpoint = self._restatement_checkpoints.get(acquisition.obligation_id)
        expected = (
            checkpoint.next_observation
            if checkpoint
            else len(
                await self.list_revisions(
                    account_id=acquisition.account_id,
                    reporting_obligation_id=acquisition.obligation_id,
                )
            )
        )
        if acquisition.ordinal != expected:
            raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
        self._provisional_acquisitions[key] = acquisition
        return acquisition
async def resolve_consumer_status_replay(self,
status: ConsumerStatusRecord) ‑> ConsumerStatusRecord | None
Expand source code
async def resolve_consumer_status_replay(
    self, status: ConsumerStatusRecord
) -> ConsumerStatusRecord | None:
    async with self._lock:
        return self._replay(status)
async def retire_issue(self,
*,
issue_key: str,
account_id: str,
at: datetime,
status_scope: ReportingStatusScope | None = None) ‑> ReportingIssueLifecycle | None
Expand source code
async def retire_issue(
    self,
    *,
    issue_key: str,
    account_id: str,
    at: datetime,
    status_scope: ReportingStatusScope | None = None,
) -> ReportingIssueLifecycle | None:
    async with self._mutation():
        key = (account_id, issue_key)
        live = self._issues.get(key)
        if live is None or not live.live or not issue_is_retirable(live.issue_state):
            # Convergent: nothing live, or already waived. A waived issue
            # is retired from the projection by agreement, and overwriting
            # that readable act with `resolved` is an edge the forward-only
            # lifecycle forbids -- enforced here rather than left to each
            # caller to remember.
            return None
        check_issue_state_transition(live.issue_state, "resolved")
        retired = replace(live, issue_state="resolved", retired_at=_utc(at))
        self._resolve_issue_scope(
            account_id=account_id,
            consumer_id=live.consumer_id,
            issue_id=live.issue_id,
            status_scope=status_scope,
        )
        self._issues[key] = retired
        self._dirty_issue(retired, status_scope, live)
        return retired
async def set_issue_state(self,
*,
issue_key: str,
account_id: str,
state: "Literal['acknowledged', 'waived']",
at: datetime,
external_ref: str | None = None,
status_scope: ReportingStatusScope | None = None) ‑> ReportingIssueLifecycle
Expand source code
async def set_issue_state(
    self,
    *,
    issue_key: str,
    account_id: str,
    state: Literal["acknowledged", "waived"],
    at: datetime,
    external_ref: str | None = None,
    status_scope: ReportingStatusScope | None = None,
) -> ReportingIssueLifecycle:
    async with self._mutation():
        if state not in {"acknowledged", "waived"}:
            raise LedgerConflictError(
                "ISSUE_STATE_NOT_OPERATOR_SETTABLE",
                f"issue_state {state!r} is not settable by an operator. 'resolved' is "
                "reachable only when the condition actually clears -- the projection "
                "retires it -- because a seller must not retire a mismatch out of a "
                "degraded projection while the statement that caused it is still the "
                "consumer's current leaf",
            )
        key = (account_id, issue_key)
        live = self._issues.get(key)
        if live is None or not live.live:
            raise LedgerConflictError(
                "ISSUE_NOT_OPEN",
                f"no open issue {issue_key!r} for this account; a retired issue cannot be "
                "reopened, and a recurrence gets a new occurrence",
            )
        check_issue_state_transition(live.issue_state, state)
        if state == "waived" and live.issue_state != "waived":
            from adcp.reporting.ledger.status_projection import bind_mismatch_waiver
            from adcp.reporting.ledger.status_snapshot import memory_snapshot

            live = bind_mismatch_waiver(memory_snapshot(self, account_id), live)
        updated = replace(
            live,
            issue_state=state,
            external_ref=external_ref or live.external_ref,
            # Set on the way into a retired state and never cleared.
            retired_at=(
                (
                    live.retired_at
                    if live.waived_reporting_status_id is not None
                    and live.retired_at is not None
                    else _utc(at)
                )
                if state == "waived"
                else live.retired_at
            ),
        )
        self._resolve_issue_scope(
            account_id=account_id,
            consumer_id=live.consumer_id,
            issue_id=live.issue_id,
            status_scope=status_scope,
        )
        self._issues[key] = updated
        # Derive the no-op from the resulting record rather than predicting
        # it: an idempotent re-acknowledge changes nothing and enqueues
        # nothing, while anything that does move retained evidence stays
        # reconstructable for the projector.
        self._dirty_issue(updated, status_scope, live)
        return updated
async def set_revision_readable(self, *, account_id: str, reporting_revision_id: str, readable: bool) ‑> None
Expand source code
async def set_revision_readable(
    self, *, account_id: str, reporting_revision_id: str, readable: bool
) -> None:
    async with self._mutation():
        existing = self._revisions.get(reporting_revision_id)
        if existing is None or existing.account_id != account_id:
            raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
        if existing.readable == readable:
            return
        self._revisions[reporting_revision_id] = replace(existing, readable=readable)
        if self._notification_state is not None:
            obligation = self._obligations[existing.reporting_obligation_id]
            self._dirty_status(
                ReportingStatusScope.for_obligation(obligation),
                "readability",
                ReportingStatusEvidence(
                    "revision", reporting_revision_id, readable=existing.readable
                ),
                ReportingStatusEvidence("revision", reporting_revision_id, readable=readable),
            )
async def transaction(self) ‑> AsyncIterator[InMemoryReportingLedgerStore]
Expand source code
@asynccontextmanager
async def transaction(self) -> AsyncIterator[InMemoryReportingLedgerStore]:
    """Group several source mutations into one atomic, replayable boundary."""
    async with self._mutation():
        yield self

Group several source mutations into one atomic, replayable boundary.

class InMemoryReportingReconciliationStore (*, clock: Callable[[], datetime] | None = None, notifications: bool = False)
Expand source code
class InMemoryReportingReconciliationStore(InMemoryReportingLedgerStore, _ReconciliationOperations):
    """Optional reference extension. No destination/receipt services at construction."""

    _delivery_records: list[tuple[int, ReportingDeliveryPrincipal, ReportingDeliveryRecord]]

    async def _commit(self, record: RecordT) -> tuple[RecordT, bool]:
        candidate = cast(RecordT, decode_record(payload(record)))
        async with self._mutation():
            return self._commit_record_unlocked(candidate)

    def _commit_record_unlocked(
        self, record: RecordT, *, notify: bool = True, dirty: bool = True
    ) -> tuple[RecordT, bool]:
        """Caller owns the memory mutation; no lock or callback is acquired here.

        ``notify`` and ``dirty`` are independent: suppressing a readiness event
        must never also drop the projection work that an ordinary public write
        has always produced.
        """
        candidate = decode_record(payload(record))
        who = principal(candidate)
        records = tuple(item.record for item in self._caller_changes(who))
        existing = replay(candidate, records)
        if existing is not None:
            return cast(RecordT, existing), False
        context = self._delivery_context(candidate)
        stored = validate_transition(candidate, records, context, self._clock())
        self._append_reconciliation_change(stored)
        if (notify or dirty) and self._notification_state is not None:
            from adcp.reporting.ledger.notification_events import (
                delivery_dirty,
                materialization_event,
            )

            if notify:
                event = materialization_event(
                    stored,
                    records,
                    context.obligation,
                    context.revision,
                    context.configuration,
                    self._clock(),
                )
                if event is not None:
                    self._record_notification(event)
            if dirty:
                scope, reason, evidence = delivery_dirty(stored, context.obligation)
                self._dirty_status(scope, reason, after=evidence)
        return cast(RecordT, stored), True

    def _append_reconciliation_change(self, record: ReportingDeliveryRecord) -> None:
        who = principal(record)
        retained = self._retained_delivery_records()
        sequence = sum(owner == who for _, owner, _ in retained) + 1
        # One assignment publishes the record, its feed row, and its local head.
        # Core's sequence and change list never participate in this transaction.
        self._delivery_records = [*retained, (sequence, who, record)]

    def _retained_delivery_records(
        self,
    ) -> list[tuple[int, ReportingDeliveryPrincipal, ReportingDeliveryRecord]]:
        # Lazily allocated so construction keeps Core's exact component surface.
        if not hasattr(self, "_delivery_records"):
            self._delivery_records = []
        return self._delivery_records

    def _caller_changes(
        self, caller: ReportingDeliveryPrincipal
    ) -> tuple[ReportingReconciliationChange, ...]:
        retained = [
            item
            for item in self._retained_delivery_records()
            if item[1] == caller or principal(item[2]) == caller
        ]
        if any(
            type(sequence) is not int
            or sequence != ordinal
            or owner != caller
            or principal(record) != caller
            for ordinal, (sequence, owner, record) in enumerate(retained, start=1)
        ):
            fail("REPORTING_HISTORY_CORRUPT")
        return tuple(
            ReportingReconciliationChange(sequence, record) for sequence, _, record in retained
        )

    def _delivery_context(self, record: ReportingDeliveryRecord) -> DeliveryContext:
        if isinstance(record, ReportingDestinationBinding):
            return DeliveryContext(configuration=self._configurations.get(record.generation_key))
        obligation = self._obligations.get(record.scope.reporting_obligation_id)
        revision_id = getattr(record, "reporting_revision_id", None)
        if isinstance(record, ReportingAdjustmentReceiptRecord):
            revision_id = record.adjusts_reporting_revision_id
        revision = self._revisions.get(revision_id) if revision_id is not None else None
        return DeliveryContext(
            configuration=self._configurations.get(record.scope.generation_key),
            obligation=obligation,
            revision=revision,
            adjustment=(
                self._adjustments.get(record.reporting_adjustment_id)
                if isinstance(record, ReportingAdjustmentReceiptRecord)
                else None
            ),
        )

    async def read_reconciliation_snapshot(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        boundary: ReportingReconciliationSnapshotToken | None = None,
    ) -> ReportingReconciliationSnapshot:
        requested = boundary is not None
        async with self._lock:
            changes = self._caller_changes(caller)
            if boundary is None:
                boundary = change_boundary(caller, len(changes), self._clock())
            if (
                type(boundary) is not ReportingReconciliationSnapshotToken
                or boundary.caller != caller
                or boundary.min_sequence != 0
                or boundary.filters != ReportingReconciliationFilter()
            ):
                unavailable()
            validate_boundary(caller, boundary, len(changes))
            if boundary.total_count != boundary.max_sequence:
                # A caller-presented boundary that disagrees with the retained feed
                # is a stale or hand-built token, never evidence that storage is
                # corrupt. Only a boundary this store opened can accuse itself.
                _boundary_unavailable(requested)
            return ReportingReconciliationSnapshot(
                caller,
                boundary,
                tuple(item.record for item in changes if item.sequence <= boundary.max_sequence),
            )

    async def read_reconciliation_changes(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        changes_after: ReportingReconciliationCheckpoint | None = None,
        cursor: ReportingReconciliationCursor | None = None,
        limit: int = 100,
        filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
    ) -> ReportingReconciliationPage:
        after, boundary, last_key = read_position(caller, changes_after, cursor, limit, filters)
        continued = boundary is not None
        async with self._lock:
            records = self._caller_changes(caller)
            if boundary is None:
                boundary = change_boundary(
                    caller,
                    len(records),
                    self._clock(),
                    after=after,
                    total_count=sum(
                        item.sequence > after and filters.matches(item.record) for item in records
                    ),
                    filters=filters,
                )
            validate_boundary(caller, boundary, len(records))
            if last_key is not None and not any(
                item.sequence == after
                and change_id(item.record) == last_key
                and filters.matches(item.record)
                for item in records
            ):
                raise LedgerConflictError("INVALID_CHECKPOINT", "reconciliation key is unavailable")
            changes = tuple(
                item
                for item in records
                if boundary.min_sequence < item.sequence <= boundary.max_sequence
                and filters.matches(item.record)
            )
            if len(changes) != boundary.total_count:
                _boundary_unavailable(continued)
            return change_page(
                caller,
                boundary,
                after,
                tuple(item for item in changes if item.sequence > after),
                limit,
            )

Optional reference extension. No destination/receipt services at construction.

Ancestors

Subclasses

Methods

async def read_reconciliation_changes(self,
*,
caller: ReportingDeliveryPrincipal,
changes_after: ReportingReconciliationCheckpoint | None = None,
cursor: ReportingReconciliationCursor | None = None,
limit: int = 100,
filters: ReportingReconciliationFilter = ReportingReconciliationFilter(record_kinds=(), reporting_obligation_id=None)) ‑> ReportingReconciliationPage
Expand source code
async def read_reconciliation_changes(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    changes_after: ReportingReconciliationCheckpoint | None = None,
    cursor: ReportingReconciliationCursor | None = None,
    limit: int = 100,
    filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
) -> ReportingReconciliationPage:
    after, boundary, last_key = read_position(caller, changes_after, cursor, limit, filters)
    continued = boundary is not None
    async with self._lock:
        records = self._caller_changes(caller)
        if boundary is None:
            boundary = change_boundary(
                caller,
                len(records),
                self._clock(),
                after=after,
                total_count=sum(
                    item.sequence > after and filters.matches(item.record) for item in records
                ),
                filters=filters,
            )
        validate_boundary(caller, boundary, len(records))
        if last_key is not None and not any(
            item.sequence == after
            and change_id(item.record) == last_key
            and filters.matches(item.record)
            for item in records
        ):
            raise LedgerConflictError("INVALID_CHECKPOINT", "reconciliation key is unavailable")
        changes = tuple(
            item
            for item in records
            if boundary.min_sequence < item.sequence <= boundary.max_sequence
            and filters.matches(item.record)
        )
        if len(changes) != boundary.total_count:
            _boundary_unavailable(continued)
        return change_page(
            caller,
            boundary,
            after,
            tuple(item for item in changes if item.sequence > after),
            limit,
        )
async def read_reconciliation_snapshot(self,
*,
caller: ReportingDeliveryPrincipal,
boundary: ReportingReconciliationSnapshotToken | None = None) ‑> ReportingReconciliationSnapshot
Expand source code
async def read_reconciliation_snapshot(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    boundary: ReportingReconciliationSnapshotToken | None = None,
) -> ReportingReconciliationSnapshot:
    requested = boundary is not None
    async with self._lock:
        changes = self._caller_changes(caller)
        if boundary is None:
            boundary = change_boundary(caller, len(changes), self._clock())
        if (
            type(boundary) is not ReportingReconciliationSnapshotToken
            or boundary.caller != caller
            or boundary.min_sequence != 0
            or boundary.filters != ReportingReconciliationFilter()
        ):
            unavailable()
        validate_boundary(caller, boundary, len(changes))
        if boundary.total_count != boundary.max_sequence:
            # A caller-presented boundary that disagrees with the retained feed
            # is a stale or hand-built token, never evidence that storage is
            # corrupt. Only a boundary this store opened can accuse itself.
            _boundary_unavailable(requested)
        return ReportingReconciliationSnapshot(
            caller,
            boundary,
            tuple(item.record for item in changes if item.sequence <= boundary.max_sequence),
        )

Inherited members

class LeasedConfiguration (account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
lease_expires_at: datetime)
Expand source code
@dataclass(frozen=True)
class LeasedConfiguration:
    """One configuration generation leased for period-close work.

    Carries its account so the producer never has to invert the lease back into
    a tenant -- an inversion that is easy to get subtly wrong when two accounts
    reuse a ``delivery_config_id``.
    """

    account_id: str
    consumer_id: str
    delivery_config_id: str
    delivery_config_version: int
    lease_expires_at: datetime

    @property
    def generation_key(self) -> ReportingConfigurationGenerationKey:
        return ReportingConfigurationGenerationKey(
            account_id=self.account_id,
            consumer_id=self.consumer_id,
            delivery_config_id=self.delivery_config_id,
            delivery_config_version=self.delivery_config_version,
        )

One configuration generation leased for period-close work.

Carries its account so the producer never has to invert the lease back into a tenant – an inversion that is easy to get subtly wrong when two accounts reuse a delivery_config_id.

Instance variables

var account_id : str
var consumer_id : str
var delivery_config_id : str
var delivery_config_version : int
prop generation_key : ReportingConfigurationGenerationKey
Expand source code
@property
def generation_key(self) -> ReportingConfigurationGenerationKey:
    return ReportingConfigurationGenerationKey(
        account_id=self.account_id,
        consumer_id=self.consumer_id,
        delivery_config_id=self.delivery_config_id,
        delivery_config_version=self.delivery_config_version,
    )
var lease_expires_at : datetime.datetime
class LedgerChange (sequence: int,
account_id: str,
record_kind: LedgerRecordKind,
record_id: str,
committed_at: datetime)
Expand source code
@dataclass(frozen=True)
class LedgerChange:
    """One append to the per-account change feed.

    The feed is what makes ``changes_after`` exact.  Every immutable record --
    obligation, revision, adjustment, consumer status -- appends here in the
    same transaction that writes it, so a consumer that persists a checkpoint
    and replays from it cannot miss a record or see one twice under a different
    identity.
    """

    sequence: int
    account_id: str
    record_kind: LedgerRecordKind
    record_id: str
    committed_at: datetime

One append to the per-account change feed.

The feed is what makes changes_after exact. Every immutable record – obligation, revision, adjustment, consumer status – appends here in the same transaction that writes it, so a consumer that persists a checkpoint and replays from it cannot miss a record or see one twice under a different identity.

Instance variables

var account_id : str
var committed_at : datetime.datetime
var record_id : str
var record_kind : Literal['obligation', 'revision', 'adjustment', 'adcp.reporting.ledger.consumer_status']
var sequence : int
class LedgerConflictError (code: str, message: str)
Expand source code
class LedgerConflictError(RuntimeError):
    """A write would violate an invariant that makes the ledger evidence.

    Carries a stable ``code`` so a handler can map it to a wire error without
    matching on prose.
    """

    def __init__(self, code: str, message: str) -> None:
        super().__init__(message)
        self.code = code

A write would violate an invariant that makes the ledger evidence.

Carries a stable code so a handler can map it to a wire error without matching on prose.

Ancestors

  • builtins.RuntimeError
  • builtins.Exception
  • builtins.BaseException
class LedgerPage (obligations: tuple[ReportingObligationRecord, ...],
revisions: tuple[ReportingRevisionRecord, ...],
adjustments: tuple[ReportingAdjustmentRecord, ...],
consumer_statuses: tuple[ConsumerStatusRecord, ...],
total_count: int,
has_more: bool,
cursor: str | None)
Expand source code
@dataclass(frozen=True)
class LedgerPage:
    """One page of the flat union of ledger records.

    Pagination is over the *flat union* of obligations, revisions, adjustments
    and consumer statuses rather than nesting history under each obligation.
    Nesting looks friendlier and is unbounded: one obligation with ten thousand
    snapshot restatements would make a single "page" unpageable.
    """

    obligations: tuple[ReportingObligationRecord, ...]
    revisions: tuple[ReportingRevisionRecord, ...]
    adjustments: tuple[ReportingAdjustmentRecord, ...]
    consumer_statuses: tuple[ConsumerStatusRecord, ...]
    total_count: int
    has_more: bool
    cursor: str | None

One page of the flat union of ledger records.

Pagination is over the flat union of obligations, revisions, adjustments and consumer statuses rather than nesting history under each obligation. Nesting looks friendlier and is unbounded: one obligation with ten thousand snapshot restatements would make a single "page" unpageable.

Instance variables

var adjustments : tuple[ReportingAdjustmentRecord, ...]
var consumer_statuses : tuple[ConsumerStatusRecord, ...]
var cursor : str | None
var has_more : bool
var obligations : tuple[ReportingObligationRecord, ...]
var revisions : tuple[ReportingRevisionRecord, ...]
var total_count : int
class LedgerSnapshot (snapshot_id: str,
account_id: str,
consumer_id: str,
ledger_as_of: datetime,
max_sequence: int)
Expand source code
@dataclass(frozen=True)
class LedgerSnapshot:
    """A consistent read boundary over one account's ledger.

    Every page reached from one cursor returns the same ``snapshot_id`` and
    ``ledger_as_of``.  Records committed *during* pagination appear after the
    returned ``changes_checkpoint`` on the next read, never inside the current
    snapshot -- otherwise a consumer's record count would not match what it
    received, and it could not tell a lost record from a late one.
    """

    snapshot_id: str
    account_id: str
    consumer_id: str
    ledger_as_of: datetime
    max_sequence: int

A consistent read boundary over one account's ledger.

Every page reached from one cursor returns the same snapshot_id and ledger_as_of. Records committed during pagination appear after the returned changes_checkpoint on the next read, never inside the current snapshot – otherwise a consumer's record count would not match what it received, and it could not tell a lost record from a late one.

Instance variables

var account_id : str
var consumer_id : str
var ledger_as_of : datetime.datetime
var max_sequence : int
var snapshot_id : str
class ObligationProjection (health: ReportingHealth,
production_status: ReportingProductionStatus,
issues: tuple[ReportingIssue, ...],
satisfied: bool,
current_revision: ReportingRevisionRecord | None)
Expand source code
@dataclass(frozen=True)
class ObligationProjection:
    """One obligation's derived status at a snapshot boundary."""

    health: ReportingHealth
    production_status: ReportingProductionStatus
    issues: tuple[ReportingIssue, ...]
    satisfied: bool
    current_revision: ReportingRevisionRecord | None

One obligation's derived status at a snapshot boundary.

Instance variables

var current_revision : ReportingRevisionRecord | None
var health : Literal['healthy', 'waiting', 'delayed', 'action_required', 'complete']
var issues : tuple[ReportingIssue, ...]
var production_status : Literal['not_due', 'pending', 'published', 'failed']
var satisfied : bool
class PgReportingLedgerStore (*,
pool: AsyncConnectionPool,
clock: Callable[[], datetime] | None = None,
notifications: bool = False)
Expand source code
class PgReportingLedgerStore:
    """Durable reporting ledger over a caller-supplied connection pool."""

    is_durable: ClassVar[bool] = True

    def __init__(
        self,
        *,
        pool: AsyncConnectionPool,
        clock: Callable[[], datetime] | None = None,
        notifications: bool = False,
    ) -> None:
        if not PG_AVAILABLE:
            raise ImportError(_INSTALL_HINT)
        self._pool = pool
        # Change timestamps and the snapshot boundary default to the *database* clock,
        # which is what makes two readers of one snapshot agree even across
        # application hosts with drifting clocks -- do not override it in
        # production for that reason. Overriding is for replay, backfill, and
        # tests that need to stand at a specific instant relative to seeded
        # evidence rather than wherever wall-clock time happens to fall.
        self._clock = clock
        self._notifications_enabled = notifications
        self._period_close_sample: tuple[int, datetime | None, str, str, str, int] | None = None

    @asynccontextmanager
    async def _connection(self) -> AsyncIterator[Any]:
        bound = _BOUND_CONNECTION.get()
        if bound is not None and bound[:2] == (self._pool, asyncio.current_task()):
            yield bound[2]
        else:
            async with self._pool.connection() as connection:
                yield connection

    @asynccontextmanager
    async def transaction(self) -> AsyncIterator[PgReportingLedgerStore]:
        """Group source operations into one committed status boundary.

        Pool ownership remains with the adopter. Nested turns are savepoints;
        callers acquire accounts in canonical order when touching several.
        """
        async with self._connection() as connection, connection.transaction():
            token = _BOUND_CONNECTION.set((self._pool, asyncio.current_task(), connection))
            try:
                yield self
            finally:
                _BOUND_CONNECTION.reset(token)

    @asynccontextmanager
    async def _source_publication(
        self, account_id: str, *, seals: ReportingSealStore | None = None
    ) -> AsyncIterator[InlineSealPublisher | None]:
        from psycopg import Error

        from adcp.reporting.inline_storage import InlineStorageError, PgReportingSealStore

        if isinstance(seals, PgReportingSealStore) and seals._pool is self._pool:
            # The backend's audited READ COMMITTED transaction owns both the
            # account lock and seal write. A second pool checkout would deadlock
            # with a size-one pool, and would separate the publication boundary.
            failure = None
            try:
                async with seals._transaction() as connection:
                    await self._lock_account(connection, account_id)
                    owner = asyncio.current_task()

                    async def publish(key: str, sealed: SealedSlice) -> SealedSlice:
                        if asyncio.current_task() is not owner:
                            raise InlineStorageError("INVALID_INPUT")
                        prepared = seals._prepare_seal(
                            account_id=account_id, source_execution_key=key, sealed=sealed
                        )
                        return await seals._put_on(connection, prepared)

                    yield publish
            except InlineStorageError as error:
                failure = error.code
            except Error:
                failure = "RESOURCE_UNAVAILABLE"
            if failure is not None:
                raise InlineStorageError(failure)
            return
        # Keep the live authorization check and ledger commit on the same
        # account transaction, including commits replayed after a restart.
        async with self.transaction(), self._connection() as connection:
            await self._lock_account(connection, account_id)
            yield None

    async def create_schema(self) -> None:
        """Create or upgrade the ledger atomically, serializing concurrent boots.

        The bootstrap takes a transaction-scoped schema lock before any DDL.
        Keep the migration in that transaction, including with an autocommit
        pool, so a second process cannot observe a partially upgraded schema.
        """
        async with self._connection() as connection:
            async with connection.transaction():
                await self._create_schema_on(connection)

    async def _create_schema_on(self, connection: Any) -> None:
        from adcp.reporting.migration import require_owned_schema_or_empty

        await require_owned_schema_or_empty(connection)
        for path in (
            _DDL_PATH,
            _ACCOUNT_GENERATIONS_DDL_PATH,
            _CURRENCY_DDL_PATH,
            _RECONCILIATION_DDL_PATH,
            _NOTIFICATIONS_DDL_PATH,
            _ACTIVITY_DDL_PATH,
            Path(__file__).with_name("reporting_provisional_observations.sql"),
            Path(__file__).with_name("reporting_caller_ownership.sql"),
        ):
            await connection.execute(path.read_text())
        await self._require_provisional_schema(connection)
        if self._notifications_enabled:
            from adcp.reporting.outbox._schema import validate_schema

            await validate_schema(connection)

    async def _require_provisional_schema(self, connection: Any) -> None:
        from adcp.reporting.outbox._schema import validate_provisional_schema

        await validate_provisional_schema(connection)

    async def _notification_now(self, connection: Any) -> datetime:
        from adcp.reporting.outbox.pg import database_now

        if self._clock is not None:
            # Preserve the explicit conformance clock across deferred boundary
            # capture. Production leaves this unset and captures database time
            # after the complete source transaction's writes, before commit.
            await connection.execute("SELECT set_config('adcp.status_clock_override', 'on', true)")
        return await database_now(connection, self._clock)

    async def _record_notification(self, connection: Any, event: ReportingDomainEvent) -> None:
        if self._notifications_enabled:
            from adcp.reporting.outbox.pg import enqueue_event

            await enqueue_event(connection, event)

    async def _dirty_status(
        self,
        connection: Any,
        scope: ReportingStatusScope,
        reason: DirtyReason,
        before: ReportingStatusEvidence | None = None,
        after: ReportingStatusEvidence | None = None,
    ) -> None:
        if self._notifications_enabled:
            from adcp.reporting.ledger.status_snapshot import settle_snapshot_on
            from adcp.reporting.outbox.pg import mark_dirty

            await settle_snapshot_on(self, connection, account_id=scope.account_id)
            await mark_dirty(
                connection, scope, reason, await self._notification_now(connection), before, after
            )

    async def _dirty_issue(
        self,
        connection: Any,
        issue: ReportingIssueLifecycle,
        status_scope: ReportingStatusScope | None,
        before: ReportingIssueLifecycle | None = None,
        *,
        enqueue: bool = True,
    ) -> None:
        from dataclasses import asdict

        has_scope_storage = await self._issue_scope_storage_on(connection)
        row = (
            await (
                await connection.execute(
                    "SELECT scope FROM reporting_issue_status_scopes"
                    " WHERE account_id = %s AND issue_id = %s",
                    (issue.account_id, issue.issue_id),
                )
            ).fetchone()
            if has_scope_storage
            else None
        )
        existing = decode_status_scope(row[0]) if row else None
        scope = (
            status_scope
            or existing
            or ReportingStatusScope(issue.account_id, consumer_id=issue.consumer_id)
        )
        if (
            scope.account_id != issue.account_id
            or issue.consumer_id is not None
            and scope.consumer_id != issue.consumer_id
        ):
            raise ReportingNotificationError("invalid_status_scope")
        validate_scope_refinement(existing, scope)
        if scope.generation_key is not None:
            key = scope.generation_key
            configuration = await (
                await connection.execute(
                    "SELECT feed_purpose FROM reporting_configurations WHERE account_id = %s"
                    " AND consumer_id = %s AND delivery_config_id = %s AND delivery_co"
                    "nfig_version = %s",
                    (
                        scope.account_id,
                        key.consumer_id,
                        key.delivery_config_id,
                        key.delivery_config_version,
                    ),
                )
            ).fetchone()
            if configuration is None or (
                scope.feed_purpose is not None and configuration[0] != scope.feed_purpose
            ):
                raise ReportingNotificationError("invalid_status_scope")
        if scope.reporting_obligation_id is not None:
            obligation = await (
                await connection.execute(
                    "SELECT delivery_config_id, delivery_config_version, feed_purpose"
                    " FROM reporting_obligations WHERE account_id = %s"
                    " AND consumer_id = %s AND reporting_obligation_id = %s",
                    (scope.account_id, scope.consumer_id, scope.reporting_obligation_id),
                )
            ).fetchone()
            if (
                obligation is None
                or (
                    scope.generation_key is not None
                    and obligation[:2]
                    != (
                        scope.generation_key.delivery_config_id,
                        scope.generation_key.delivery_config_version,
                    )
                )
                or (scope.feed_purpose is not None and obligation[2] != scope.feed_purpose)
            ):
                raise ReportingNotificationError("invalid_status_scope")
        if not has_scope_storage:
            # Default-off Core writers also support the pre-outbox schema.
            # Migration introduces scope persistence without rewriting history.
            return
        await connection.execute(
            "INSERT INTO reporting_issue_status_scopes (account_id, issue_id, scope)"
            " VALUES (%s,%s,%s::jsonb) ON CONFLICT (account_id, issue_id)"
            " DO UPDATE SET scope = EXCLUDED.scope",
            (issue.account_id, issue.issue_id, _json(asdict(scope))),
        )
        if enqueue and (before != issue or existing != scope):
            await self._dirty_status(
                connection,
                scope,
                "issue",
                issue_evidence(before) if before else None,
                issue_evidence(issue),
            )

    async def _issue_scope_storage_on(self, connection: Any) -> bool:
        row = await (
            await connection.execute(
                "SELECT to_regclass(quote_ident(current_schema())"
                " || '.reporting_issue_status_scopes')"
            )
        ).fetchone()
        present = row is not None and row[0] is not None
        if not present and self._notifications_enabled:
            raise ReportingNotificationError(
                "notification_schema_unready:missing:reporting_issue_status_scopes"
            )
        return present

    # -- change feed ------------------------------------------------------

    async def _append_change(
        self,
        connection: Any,
        account_id: str,
        kind: LedgerRecordKind,
        record_id: str,
        *,
        consumer_id: str,
    ) -> None:
        await connection.execute(
            "INSERT INTO reporting_ledger_changes"
            " (account_id, consumer_id, record_kind, record_id, committed_at)"
            " VALUES (%s, %s, %s, %s, COALESCE(%s, now()))"
            " ON CONFLICT (account_id, consumer_id, record_kind, record_id) DO NOTHING",
            (
                account_id,
                consumer_id,
                kind,
                record_id,
                self._clock() if self._clock is not None else None,
            ),
        )

    @staticmethod
    async def _bound_lock_waits(connection: Any) -> None:
        # set_config(..., true) is SET LOCAL: the cap ends with this transaction
        # (or savepoint rollback) and never lengthens an adopter's shorter limit.
        # SKIP LOCKED does not skip relation, FK, trigger, or advisory-lock waits.
        await connection.execute(
            "SELECT set_config('lock_timeout',"
            " LEAST(COALESCE(NULLIF(setting::integer,0),5000),5000)::text,true)"
            " FROM pg_settings WHERE name='lock_timeout'"
        )

    def _worker_lock_errors(self) -> tuple[type[Exception], ...]:
        from psycopg.errors import DeadlockDetected, LockNotAvailable

        bound = _BOUND_CONNECTION.get()
        if bound is not None and bound[:2] == (self._pool, asyncio.current_task()):
            # The adopter owns the outer rollback and any locks acquired before
            # our savepoint. Only standalone worker turns may reacquire a lease.
            return ()
        return (DeadlockDetected, LockNotAvailable)

    @staticmethod
    async def _lock_account(connection: Any, account_id: str) -> None:
        """Serialize this account's feed appends so seq order == commit order."""
        await PgReportingLedgerStore._bound_lock_waits(connection)
        await connection.execute(
            "SELECT pg_advisory_xact_lock(hashtext(%s))", (f"adcp.reporting:{account_id}",)
        )
        # Optional C lock order: account, boundary cursor, canonical typed scopes,
        # then source evidence. A/B schemas and custom stores need no C methods.
        present = await (
            await connection.execute(
                "SELECT to_regclass(quote_ident(current_schema()) || '.reporting_status_accounts')"
            )
        ).fetchone()
        if present is not None and present[0] is not None:
            await (
                await connection.execute(
                    "SELECT 1 FROM reporting_status_accounts WHERE account_id=%s FOR UPDATE",
                    (account_id,),
                )
            ).fetchall()
            await (
                await connection.execute(
                    "SELECT 1 FROM reporting_status_scope_checkpoints WHERE account_id=%s"
                    " ORDER BY account_id, consumer_namespace, delivery_config_id, version,"
                    " scope_kind, obligation_namespace FOR UPDATE",
                    (account_id,),
                )
            ).fetchall()

    # -- configurations ---------------------------------------------------

    async def put_configuration(self, configuration: ReportingConfiguration) -> None:
        reject_reserved_authoritative_party(configuration)
        key = configuration.generation_key
        payload = _configuration_payload(configuration)
        digest = _fingerprint(payload)
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, key.account_id)
            inserted_cursor = await connection.execute(
                "INSERT INTO reporting_configurations"
                " (delivery_config_id, delivery_config_version, account_id,"
                "  report_definition_id, reporting_profile, feed_purpose, required_finality,"
                "  account_timezone, schedule, media_buy_ids, activated_at, deactivated_at,"
                "  automated_recovery_seconds, status_retention_days, definition,"
                "  authoritative_party, content_sha256, consumer_id, quarantined)"
                " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s::jsonb, %s, %s, %s, %s,"
                "         %s::jsonb, %s, %s, %s, %s)"
                " ON CONFLICT (account_id, consumer_id, delivery_config_id, delive"
                "ry_config_version) DO NOTHING"
                " RETURNING delivery_config_id",
                (
                    key.delivery_config_id,
                    key.delivery_config_version,
                    key.account_id,
                    configuration.report_definition_id,
                    configuration.reporting_profile,
                    configuration.feed_purpose,
                    configuration.required_finality,
                    configuration.account_timezone,
                    _json(payload["schedule"]),
                    _json(sorted(configuration.media_buy_ids)),
                    configuration.activated_at,
                    configuration.deactivated_at,
                    configuration.automated_recovery_window.total_seconds(),
                    configuration.status_retention_days,
                    _json(payload["definition"]) if payload["definition"] else None,
                    configuration.authoritative_party,
                    digest,
                    configuration.consumer_id,
                    configuration.quarantined,
                ),
            )
            inserted = await inserted_cursor.fetchone()
            # Check *after* the insert. ON CONFLICT waits for a concurrent
            # winner; a fresh READ COMMITTED statement sees its retained
            # content. A pre-insert check followed by DO NOTHING could silently
            # accept a different immutable generation from a losing writer.
            row = await (
                await connection.execute(
                    "SELECT content_sha256, activated_at, deactivated_at,"
                    " automated_recovery_seconds, status_retention_days, quarantined"
                    " FROM reporting_configurations"
                    " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                    " AND delivery_config_version = %s FOR UPDATE",
                    (
                        key.account_id,
                        key.consumer_id,
                        key.delivery_config_id,
                        key.delivery_config_version,
                    ),
                )
            ).fetchone()
            if row is not None and row[5]:
                raise LedgerConflictError(
                    "REPORTING_GENERATION_QUARANTINED", "legacy evidence is read-only"
                )
            if row is None or row[0] != digest:
                raise LedgerConflictError(
                    "CONFIGURATION_GENERATION_IMMUTABLE",
                    f"configuration {key.delivery_config_id}@{key.delivery_config_version} "
                    "already exists with different content for this account; publish a new "
                    "version instead of editing a retained generation",
                )

            scope = ReportingStatusScope(configuration.account_id, configuration.generation_key)
            if inserted is not None:
                if self._notifications_enabled:
                    await self._dirty_status(
                        connection,
                        scope,
                        "configuration",
                        after=configuration_evidence(configuration),
                    )
                return
            # rc.3 carries activation/deactivation and the recovery/retention
            # windows as lifecycle state over one immutable generation, so a
            # re-put that changes only those must apply -- otherwise a
            # deactivated feed keeps minting obligations -- and must co-commit
            # its status-dirty generation on this exact connection. An
            # unchanged re-put stays a no-op and enqueues nothing.
            retained = replace(
                configuration,
                activated_at=_utc(row[1]) if row[1] else None,
                deactivated_at=_utc(row[2]) if row[2] else None,
                automated_recovery_window=timedelta(seconds=float(row[3])),
                status_retention_days=row[4],
            )
            if configuration_lifecycle(retained) == configuration_lifecycle(configuration):
                return
            await connection.execute(
                "UPDATE reporting_configurations SET activated_at = %s, deactivated_at = %s,"
                " automated_recovery_seconds = %s, status_retention_days = %s"
                " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                " AND delivery_config_version = %s",
                (
                    configuration.activated_at,
                    configuration.deactivated_at,
                    configuration.automated_recovery_window.total_seconds(),
                    configuration.status_retention_days,
                    key.account_id,
                    key.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                ),
            )
            if self._notifications_enabled:
                await self._dirty_status(
                    connection,
                    scope,
                    "configuration",
                    before=configuration_evidence(retained),
                    after=configuration_evidence(configuration),
                )

    async def list_configurations(
        self, *, caller: ReportingCaller, delivery_config_ids: Sequence[str] | None = None
    ) -> tuple[ReportingConfiguration, ...]:
        async with self._connection() as connection:
            return await self._list_configurations_on(
                connection,
                account_id=caller.account_id,
                consumer_id=caller.consumer_id,
                delivery_config_ids=delivery_config_ids,
            )

    async def list_all_configurations(self) -> tuple[ReportingConfiguration, ...]:
        """One ordered scan for trusted service recovery, including retired generations."""
        async with self._connection() as connection:
            rows = await (
                await connection.execute(
                    "SELECT delivery_config_id, delivery_config_version, account_id,"
                    " report_definition_id, reporting_profile, feed_purpose, required_finality,"
                    " account_timezone, schedule, media_buy_ids, activated_at, deactivated_at,"
                    " automated_recovery_seconds, status_retention_days, definition,"
                    " authoritative_party, consumer_id, quarantined"
                    " FROM reporting_configurations WHERE NOT quarantined"
                    " ORDER BY account_id, consumer_id, delivery_config_id, delivery_config_version"
                )
            ).fetchall()
        return tuple(_configuration_from_row(row) for row in rows)

    @staticmethod
    async def _list_configurations_on(
        connection: Any,
        *,
        account_id: str,
        consumer_id: str,
        delivery_config_ids: Sequence[str] | None = None,
    ) -> tuple[ReportingConfiguration, ...]:
        clause = " AND delivery_config_id = ANY(%s)" if delivery_config_ids else ""
        params: list[Any] = [account_id, consumer_id]
        if delivery_config_ids:
            params.append(list(delivery_config_ids))
        rows = await (
            await connection.execute(
                "SELECT delivery_config_id, delivery_config_version, account_id,"  # noqa: S608  # nosec B608
                " report_definition_id, reporting_profile, feed_purpose, required_finality,"
                " account_timezone, schedule, media_buy_ids, activated_at, deactivated_at,"
                " automated_recovery_seconds, status_retention_days, definition,"
                " authoritative_party, consumer_id, quarantined"
                " FROM reporting_configurations"
                f" WHERE account_id = %s AND consumer_id = %s{clause}"  # noqa: S608 — clause is a literal
                " ORDER BY delivery_config_id, delivery_config_version",
                tuple(params),
            )
        ).fetchall()
        return tuple(_configuration_from_row(row) for row in rows)

    @staticmethod
    async def _require_writable_generation(
        connection: Any, key: ReportingConfigurationGenerationKey
    ) -> None:
        row = await (
            await connection.execute(
                "SELECT quarantined FROM reporting_configurations WHERE account_id=%s"
                " AND consumer_id=%s AND delivery_config_id=%s AND delivery_config_version=%s",
                (
                    key.account_id,
                    key.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                ),
            )
        ).fetchone()
        if row is None:
            raise LedgerConflictError(
                "UNKNOWN_CONFIGURATION_GENERATION", "generation is unavailable"
            )
        if row[0]:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED",
                "establish a new owned generation after operator reconciliation",
            )

    # -- obligations ------------------------------------------------------

    async def commit_obligation(
        self, obligation: ReportingObligationRecord
    ) -> ReportingObligationRecord:
        key = obligation.generation_key
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, key.account_id)
            await self._require_writable_generation(connection, key)
            existing_row = await (
                await connection.execute(
                    f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                    " AND delivery_config_version = %s AND period_start = %s AND period_end = %s",
                    (
                        key.account_id,
                        key.consumer_id,
                        key.delivery_config_id,
                        key.delivery_config_version,
                        obligation.period.start,
                        obligation.period.end,
                    ),
                )
            ).fetchone()
            if existing_row is not None:
                return _obligation_from_row(existing_row)
            require_frozen_currency(obligation.currency)
            try:
                inserted = await (
                    await connection.execute(
                        "INSERT INTO reporting_obligations"
                        " (reporting_obligation_id, account_id, delivery_config_id,"
                        "  delivery_config_version, report_definition_id, reporting_profile,"
                        "  feed_purpose, period_key, period_start, period_end, source_timezone,"
                        "  expected_at, scope_resolved_at, automated_recovery_deadline_at,"
                        "  required_finality, coverage_status, media_buy_ids, package_ids,"
                        "  schedule, definition, created_at, currency, consumer_id)"
                        " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,"
                        "         %s::jsonb, %s::jsonb, %s::jsonb, %s::jsonb, %s, %s, %s)"
                        " ON CONFLICT (account_id, consumer_id, delivery_config_id, delive"
                        "ry_config_version,"
                        "              period_start, period_end) DO NOTHING"
                        " RETURNING reporting_obligation_id",
                        (
                            obligation.reporting_obligation_id,
                            key.account_id,
                            key.delivery_config_id,
                            key.delivery_config_version,
                            obligation.report_definition_id,
                            obligation.reporting_profile,
                            obligation.feed_purpose,
                            obligation.period.period_key,
                            obligation.period.start,
                            obligation.period.end,
                            obligation.period.source_timezone,
                            obligation.period.expected_at,
                            obligation.scope_resolved_at,
                            obligation.automated_recovery_deadline_at,
                            obligation.required_finality,
                            obligation.coverage_status,
                            _json(sorted(obligation.media_buy_ids)),
                            _json(sorted(obligation.package_ids)),
                            _json(_schedule_payload(obligation.schedule)),
                            (
                                _json(_definition_payload(obligation.definition))
                                if obligation.definition
                                else None
                            ),
                            obligation.created_at,
                            obligation.currency,
                            obligation.consumer_id,
                        ),
                    )
                ).fetchone()
            except Exception as error:
                raise _translate_integrity_error(error) from error
            if inserted is not None:
                await self._append_change(
                    connection,
                    obligation.account_id,
                    "obligation",
                    obligation.reporting_obligation_id,
                    consumer_id=obligation.consumer_id,
                )
                if self._notifications_enabled:
                    await self._dirty_status(
                        connection,
                        ReportingStatusScope.for_obligation(obligation),
                        "obligation",
                        after=ReportingStatusEvidence(
                            "obligation", obligation.reporting_obligation_id
                        ),
                    )
                return obligation
        # Another worker won the period close; converge on its obligation.
        existing = await self.find_obligation(
            account_id=obligation.account_id,
            consumer_id=obligation.consumer_id,
            delivery_config_id=obligation.delivery_config_id,
            delivery_config_version=obligation.delivery_config_version,
            period_start=obligation.period.start,
            period_end=obligation.period.end,
        )
        if existing is None:  # pragma: no cover - only under concurrent deletion
            raise LedgerConflictError(
                "OBLIGATION_LOST", "the obligation vanished between insert and read"
            )
        return existing

    async def get_obligation(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> ReportingObligationRecord | None:
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND reporting_obligation_id = %s",
                    (account_id, reporting_obligation_id),
                )
            ).fetchone()
        return _obligation_from_row(row) if row else None

    async def find_obligation(
        self,
        *,
        account_id: str,
        consumer_id: str,
        delivery_config_id: str,
        delivery_config_version: int,
        period_start: datetime,
        period_end: datetime,
    ) -> ReportingObligationRecord | None:
        key = ReportingConfigurationGenerationKey(
            account_id=account_id,
            consumer_id=consumer_id,
            delivery_config_id=delivery_config_id,
            delivery_config_version=delivery_config_version,
        )
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                    " AND delivery_config_version = %s AND period_start = %s AND period_end = %s",
                    (
                        key.account_id,
                        key.consumer_id,
                        key.delivery_config_id,
                        key.delivery_config_version,
                        period_start,
                        period_end,
                    ),
                )
            ).fetchone()
        return _obligation_from_row(row) if row else None

    # -- revisions --------------------------------------------------------

    async def commit_revision(
        self, revision: ReportingRevisionRecord, rows: Sequence[dict[str, Any]]
    ) -> ReportingRevisionRecord:
        rows = tuple(deepcopy(row) for row in rows)
        if revision.row_count != len(rows):
            raise LedgerConflictError(
                "ROW_COUNT_MISMATCH",
                f"revision declares {revision.row_count} rows but {len(rows)} were supplied",
            )
        validate_managed_revision_rows(revision, rows)
        digest = _fingerprint(_revision_payload(revision))
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, revision.account_id)
            existing = await (
                await connection.execute(
                    f"SELECT {_REVISION_COLUMNS}, content_sha256 FROM reporting_revisions"  # noqa: S608  # nosec B608
                    " WHERE reporting_revision_id = %s AND account_id = %s",
                    (revision.reporting_revision_id, revision.account_id),
                )
            ).fetchone()
            if existing is not None:
                if existing[-1] != digest:
                    raise LedgerConflictError(
                        "REVISION_IMMUTABLE",
                        f"revision {revision.reporting_revision_id} already exists with "
                        "different content; a restatement is a new revision",
                    )
                return _revision_from_row(existing[:-1])
            obligation = await (
                await connection.execute(
                    f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                    " WHERE reporting_obligation_id = %s AND account_id = %s",
                    (revision.reporting_obligation_id, revision.account_id),
                )
            ).fetchone()
            if obligation is None:
                raise LedgerConflictError(
                    "OBLIGATION_NOT_FOUND",
                    "a revision must attach to an obligation committed at the period close",
                )
            await self._require_writable_generation(
                connection, _obligation_from_row(obligation).generation_key
            )
            validate_revision_currency(_obligation_from_row(obligation), revision, rows)
            if revision.supersedes_reporting_revision_id:
                await self._require_current_leaf(connection, revision)
            write_error = None
            try:
                await connection.execute(
                    "INSERT INTO reporting_revisions"
                    " (reporting_revision_id, account_id, reporting_obligation_id, finality,"
                    "  revision_content_sha256, row_count, control_totals, observed_at,"
                    "  data_through, created_at, supersedes_reporting_revision_id,"
                    "  finality_basis, finality_policy_id, finalized_at, readable,"
                    "  readable_at_commit, source_publication_id, source_manifest_sha256,"
                    "  content_sha256, canonical_content_digest, managed_control_totals)"
                    " VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s, %s, %s, %s, %s,"
                    "         %s, %s, %s, %s, %s, %s::jsonb, %s::jsonb)",
                    (
                        revision.reporting_revision_id,
                        revision.account_id,
                        revision.reporting_obligation_id,
                        revision.finality,
                        revision.revision_content_sha256,
                        revision.row_count,
                        _json([[name, value] for name, value in revision.control_totals]),
                        revision.observed_at,
                        revision.data_through,
                        revision.created_at,
                        revision.supersedes_reporting_revision_id,
                        revision.finality_basis,
                        revision.finality_policy_id,
                        revision.finalized_at,
                        revision.readable,
                        revision.readable_at_commit,
                        revision.source_publication_id,
                        revision.source_manifest_sha256,
                        digest,
                        (
                            _json(revision.canonical_content_digest.to_wire())
                            if revision.canonical_content_digest is not None
                            else None
                        ),
                        (
                            _json([item.to_wire() for item in revision.managed_control_totals])
                            if revision.managed_control_totals is not None
                            else None
                        ),
                    ),
                )
            except Exception as error:  # psycopg raises UniqueViolation subclasses
                write_error = _translate_integrity_error(error)
            if write_error is not None:
                raise write_error
            if rows:
                await connection.cursor().executemany(
                    "INSERT INTO reporting_revision_rows"
                    " (reporting_revision_id, ordinal, row_payload)"
                    " SELECT reporting_revision_id, %s, %s::jsonb FROM reporting_revisions"
                    " WHERE account_id = %s AND reporting_revision_id = %s",
                    [
                        (ordinal, _json(row), revision.account_id, revision.reporting_revision_id)
                        for ordinal, row in enumerate(rows)
                    ],
                )
            await self._append_change(
                connection,
                revision.account_id,
                "revision",
                revision.reporting_revision_id,
                consumer_id=_obligation_from_row(obligation).consumer_id,
            )
            if self._notifications_enabled:
                from adcp.reporting.ledger.notification_events import revision_event

                await self._record_notification(
                    connection,
                    revision_event(
                        revision,
                        await self._notification_now(connection),
                        consumer_id=_obligation_from_row(obligation).consumer_id,
                    ),
                )
                await self._dirty_status(
                    connection,
                    ReportingStatusScope.for_obligation(_obligation_from_row(obligation)),
                    "revision",
                    after=ReportingStatusEvidence(
                        "revision",
                        revision.reporting_revision_id,
                        readable=revision.readable,
                        supersedes_id=revision.supersedes_reporting_revision_id,
                    ),
                )
        return revision

    @staticmethod
    async def _require_current_leaf(connection: Any, revision: ReportingRevisionRecord) -> None:
        target = revision.supersedes_reporting_revision_id
        row = await (
            await connection.execute(
                "SELECT r.reporting_revision_id,"
                " EXISTS (SELECT 1 FROM reporting_revisions s"
                "         WHERE s.account_id = r.account_id"
                "           AND s.supersedes_reporting_revision_id = r.reporting_revision_id)"
                " FROM reporting_revisions r"
                " WHERE r.reporting_revision_id = %s AND r.account_id = %s"
                "   AND r.reporting_obligation_id = %s",
                (target, revision.account_id, revision.reporting_obligation_id),
            )
        ).fetchone()
        if row is None:
            raise LedgerConflictError(
                "SUPERSEDES_UNKNOWN", f"revision {target} is not part of this obligation's chain"
            )
        if row[1]:
            raise LedgerConflictError(
                "SUPERSEDES_STALE",
                f"revision {target} has already been superseded; a stale pointer would fork "
                "the chain and let a successful retry erase a recorded restatement",
            )

    async def list_revisions(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> tuple[ReportingRevisionRecord, ...]:
        async with self._connection() as connection:
            rows = await (
                await connection.execute(
                    f"SELECT {_REVISION_COLUMNS} FROM reporting_revisions"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND reporting_obligation_id = %s"
                    " ORDER BY created_at, reporting_revision_id",
                    (account_id, reporting_obligation_id),
                )
            ).fetchall()
        return tuple(_revision_from_row(row) for row in rows)

    async def get_provisional_acquisition(
        self, *, account_id: str, reporting_obligation_id: str, ordinal: int
    ) -> ProvisionalAcquisition | None:
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    "SELECT payload FROM reporting_provisional_acquisitions"
                    " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                    (account_id, reporting_obligation_id, ordinal),
                )
            ).fetchone()
        return ProvisionalAcquisition.from_wire(row[0]) if row is not None else None

    async def reserve_provisional_acquisition(
        self, acquisition: ProvisionalAcquisition
    ) -> ProvisionalAcquisition:
        async with self.transaction(), self._connection() as connection:
            await self._lock_account(connection, acquisition.account_id)
            await self._require_provisional_schema(connection)
            key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
            existing = await (
                await connection.execute(
                    "SELECT payload FROM reporting_provisional_acquisitions"
                    " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                    key,
                )
            ).fetchone()
            if existing is not None:
                return ProvisionalAcquisition.from_wire(existing[0])
            obligation = await self.get_obligation(
                account_id=acquisition.account_id,
                reporting_obligation_id=acquisition.obligation_id,
            )
            if obligation is None:
                raise LedgerConflictError("OBLIGATION_NOT_FOUND", "unknown observation obligation")
            if not acquisition.binds(obligation):
                raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition generation differs")
            duplicate = await (
                await connection.execute(
                    "SELECT 1 FROM reporting_provisional_acquisitions"
                    " WHERE account_id=%s AND source_execution_key=%s",
                    (acquisition.account_id, acquisition.execution_key),
                )
            ).fetchone()
            if duplicate is not None:
                raise LedgerConflictError(
                    "OBSERVATION_CONFLICT", "execution key is already reserved"
                )
            checkpoint = await self.get_restatement_checkpoint(
                account_id=acquisition.account_id,
                reporting_obligation_id=acquisition.obligation_id,
            )
            expected = (
                checkpoint.next_observation
                if checkpoint
                else len(
                    await self.list_revisions(
                        account_id=acquisition.account_id,
                        reporting_obligation_id=acquisition.obligation_id,
                    )
                )
            )
            if acquisition.ordinal != expected:
                raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
            await connection.execute(
                "INSERT INTO reporting_provisional_acquisitions"
                " (account_id,reporting_obligation_id,ordinal,source_execution_key,payload)"
                " VALUES (%s,%s,%s,%s,%s::jsonb)",
                (*key, acquisition.execution_key, _json(acquisition.to_wire())),
            )
            return ProvisionalAcquisition.from_wire(acquisition.to_wire())

    async def get_provisional_observation(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> ProvisionalObservation | None:
        async with self._connection() as connection:
            await self._require_provisional_schema(connection)
            row = await (
                await connection.execute(
                    "SELECT payload FROM reporting_provisional_observations"
                    " WHERE account_id=%s AND reporting_obligation_id=%s"
                    " ORDER BY ordinal DESC LIMIT 1",
                    (account_id, reporting_obligation_id),
                )
            ).fetchone()
        return ProvisionalObservation.from_wire(row[0]) if row else None

    async def commit_provisional_observation(
        self,
        observation: ProvisionalObservation,
        revision: ReportingRevisionRecord,
        rows: Sequence[dict[str, Any]],
    ) -> ReportingRevisionRecord:
        acquisition = observation.acquisition
        key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
        if (
            revision.account_id != acquisition.account_id
            or revision.reporting_obligation_id != acquisition.obligation_id
            or revision.reporting_revision_id != observation.revision_id
        ):
            raise LedgerConflictError("OBSERVATION_CONFLICT", "observation identity differs")
        async with self.transaction(), self._connection() as connection:
            await self._lock_account(connection, acquisition.account_id)
            await self._require_provisional_schema(connection)
            existing = await (
                await connection.execute(
                    "SELECT reporting_revision_id,payload FROM reporting_provisional_observations"
                    " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                    key,
                )
            ).fetchone()
            if existing is not None:
                retained_observation = ProvisionalObservation.from_wire(existing[1])
                if (
                    retained_observation.acquisition != acquisition
                    or existing[0] != observation.revision_id
                ):
                    raise LedgerConflictError("OBSERVATION_CONFLICT", "observation replay differs")
                retained_revision = await self.get_revision(
                    account_id=acquisition.account_id, reporting_revision_id=existing[0]
                )
                if retained_revision is None:
                    raise LedgerConflictError(
                        "HISTORY_UNAVAILABLE", "observation revision is missing"
                    )
                # Reuse the immutable publication replay checks while holding
                # the observation's account lock and transaction.
                return await self.commit_revision(revision, rows)
            reserved = await (
                await connection.execute(
                    "SELECT payload FROM reporting_provisional_acquisitions"
                    " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                    key,
                )
            ).fetchone()
            if reserved is None or ProvisionalAcquisition.from_wire(reserved[0]) != acquisition:
                raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition was not reserved")
            checkpoint = await self.get_restatement_checkpoint(
                account_id=acquisition.account_id,
                reporting_obligation_id=acquisition.obligation_id,
            )
            if checkpoint is not None and checkpoint.next_observation != acquisition.ordinal:
                raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
            committed = await self.commit_revision(revision, rows)
            await self.record_restatement_checkpoint(
                RestatementCheckpoint(
                    acquisition.account_id,
                    acquisition.obligation_id,
                    observation.checked_at,
                    acquisition.ordinal + 1,
                    observation.provisional_until,
                )
            )
            await connection.execute(
                "INSERT INTO reporting_provisional_observations"
                " (account_id,reporting_obligation_id,ordinal,reporting_revision_id,payload)"
                " VALUES (%s,%s,%s,%s,%s::jsonb)",
                (*key, revision.reporting_revision_id, _json(observation.to_wire())),
            )
            return committed

    async def get_retry_schedule(self, *, scope_key: str) -> RetryScheduleEntry | None:
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    "SELECT retry_not_before, attempt, blocked, recorded_at"
                    " FROM adcp_reporting_producer_retry_schedules"
                    " WHERE scope_key = %s",
                    (scope_key,),
                )
            ).fetchone()
        return (
            RetryScheduleEntry(scope_key, _utc(row[0]), int(row[1]), row[2], _utc(row[3]))
            if row
            else None
        )

    async def record_retry_schedule(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    "INSERT INTO adcp_reporting_producer_retry_schedules"
                    " (scope_key, retry_not_before, attempt, blocked, recorded_at)"
                    " VALUES (%s, %s, %s, %s, %s)"
                    " ON CONFLICT (scope_key) DO UPDATE SET"
                    " retry_not_before = CASE WHEN EXCLUDED.attempt = 0 OR EXCLUDED.blocked"
                    " OR adcp_reporting_producer_retry_schedules.blocked OR %s"
                    " THEN EXCLUDED.retry_not_before ELSE GREATEST("
                    " adcp_reporting_producer_retry_schedules.retry_not_before,"
                    " EXCLUDED.retry_not_before) END,"
                    " attempt = EXCLUDED.attempt, blocked = EXCLUDED.blocked,"
                    " recorded_at = EXCLUDED.recorded_at"
                    " WHERE EXCLUDED.recorded_at >="
                    " adcp_reporting_producer_retry_schedules.recorded_at"
                    " AND (EXCLUDED.attempt = 0 OR EXCLUDED.blocked OR %s"
                    " OR (NOT adcp_reporting_producer_retry_schedules.blocked AND"
                    " adcp_reporting_producer_retry_schedules.attempt <= EXCLUDED.attempt))"
                    " RETURNING retry_not_before, attempt, blocked, recorded_at",
                    (
                        entry.scope_key,
                        entry.retry_not_before,
                        entry.attempt,
                        entry.blocked,
                        entry.recorded_at,
                        entry.replayed,
                        entry.replayed,
                    ),
                )
            ).fetchone()
        if row is None:
            stored = await self.get_retry_schedule(scope_key=entry.scope_key)
            assert stored is not None
            return stored
        return RetryScheduleEntry(entry.scope_key, _utc(row[0]), int(row[1]), row[2], _utc(row[3]))

    async def get_restatement_checkpoint(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> RestatementCheckpoint | None:
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    "SELECT account_id, reporting_obligation_id, checked_at,"
                    " next_observation, provisional_until"
                    " FROM reporting_restatement_checkpoints"
                    " WHERE account_id = %s AND reporting_obligation_id = %s",
                    (account_id, reporting_obligation_id),
                )
            ).fetchone()
        if row is None:
            return None
        return RestatementCheckpoint(
            account_id=row[0],
            reporting_obligation_id=row[1],
            checked_at=_utc(row[2]),
            next_observation=int(row[3]),
            provisional_until=_utc(row[4]) if row[4] else None,
        )

    async def record_restatement_checkpoint(
        self, checkpoint: RestatementCheckpoint
    ) -> RestatementCheckpoint:
        async with self._connection() as connection:
            obligation = await (
                await connection.execute(
                    "SELECT 1 FROM reporting_obligations"
                    " WHERE reporting_obligation_id = %s AND account_id = %s",
                    (checkpoint.reporting_obligation_id, checkpoint.account_id),
                )
            ).fetchone()
            if obligation is None:
                raise LedgerConflictError(
                    "OBLIGATION_NOT_FOUND",
                    "a restatement checkpoint must attach to an obligation for this account",
                )
            row = await (
                await connection.execute(
                    "INSERT INTO reporting_restatement_checkpoints"
                    " (account_id, reporting_obligation_id, checked_at, next_observation,"
                    "  provisional_until)"
                    " VALUES (%s, %s, %s, %s, %s)"
                    " ON CONFLICT (reporting_obligation_id) DO UPDATE SET"
                    " checked_at = EXCLUDED.checked_at,"
                    " next_observation = EXCLUDED.next_observation,"
                    " provisional_until = EXCLUDED.provisional_until"
                    " WHERE reporting_restatement_checkpoints.account_id = EXCLUDED.account_id"
                    "   AND (reporting_restatement_checkpoints.next_observation"
                    "        < EXCLUDED.next_observation"
                    "     OR (reporting_restatement_checkpoints.next_observation"
                    "         = EXCLUDED.next_observation"
                    "         AND reporting_restatement_checkpoints.checked_at"
                    "             < EXCLUDED.checked_at))"
                    " RETURNING account_id, reporting_obligation_id, checked_at,"
                    " next_observation, provisional_until",
                    (
                        checkpoint.account_id,
                        checkpoint.reporting_obligation_id,
                        checkpoint.checked_at,
                        checkpoint.next_observation,
                        checkpoint.provisional_until,
                    ),
                )
            ).fetchone()
        if row is None:
            stored = await self.get_restatement_checkpoint(
                account_id=checkpoint.account_id,
                reporting_obligation_id=checkpoint.reporting_obligation_id,
            )
            if stored is None:
                raise LedgerConflictError(
                    "OBLIGATION_NOT_FOUND",
                    "a restatement checkpoint must attach to an obligation for this account",
                )
            return stored
        return RestatementCheckpoint(
            account_id=row[0],
            reporting_obligation_id=row[1],
            checked_at=_utc(row[2]),
            next_observation=int(row[3]),
            provisional_until=_utc(row[4]) if row[4] else None,
        )

    async def get_revision(
        self, *, account_id: str, reporting_revision_id: str
    ) -> ReportingRevisionRecord | None:
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    f"SELECT {_REVISION_COLUMNS} FROM reporting_revisions"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND reporting_revision_id = %s",
                    (account_id, reporting_revision_id),
                )
            ).fetchone()
        return _revision_from_row(row) if row else None

    async def read_revision_rows(
        self,
        *,
        account_id: str,
        reporting_revision_id: str,
        cursor: str | None = None,
        limit: int = 500,
    ) -> ReportingRowPage:
        from adcp.reporting.ledger.store import revision_row_offset

        offset = revision_row_offset(cursor, reporting_revision_id, limit)
        async with self._connection() as connection:
            owned = await (
                await connection.execute(
                    "SELECT row_count FROM reporting_revisions"
                    " WHERE account_id = %s AND reporting_revision_id = %s",
                    (account_id, reporting_revision_id),
                )
            ).fetchone()
            if owned is None:
                raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
            rows = await (
                await connection.execute(
                    "SELECT row_payload FROM reporting_revision_rows"
                    " WHERE reporting_revision_id = %s"
                    " ORDER BY ordinal OFFSET %s LIMIT %s",
                    (reporting_revision_id, offset, limit),
                )
            ).fetchall()
        total = int(owned[0])
        has_more = offset + limit < total
        return ReportingRowPage(
            reporting_revision_id=reporting_revision_id,
            rows=tuple(row[0] for row in rows),
            total_count=total,
            has_more=has_more,
            cursor=(
                encode_cursor(
                    {"ownership": 2, "revision": reporting_revision_id, "offset": offset + limit}
                )
                if has_more
                else None
            ),
        )

    async def set_revision_readable(
        self, *, account_id: str, reporting_revision_id: str, readable: bool
    ) -> None:
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, account_id)
            existing = await (
                await connection.execute(
                    "SELECT readable, reporting_obligation_id FROM reporting_revisions"
                    " WHERE account_id = %s AND reporting_revision_id = %s FOR UPDATE",
                    (account_id, reporting_revision_id),
                )
            ).fetchone()
            if existing is None:
                raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
            if existing[0] == readable:
                return
            await connection.execute(
                "UPDATE reporting_revisions SET readable = %s"
                " WHERE account_id = %s AND reporting_revision_id = %s",
                (readable, account_id, reporting_revision_id),
            )
            if self._notifications_enabled:
                obligation = await (
                    await connection.execute(
                        f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # nosec B608
                        " WHERE account_id = %s AND reporting_obligation_id = %s",
                        (account_id, existing[1]),
                    )
                ).fetchone()
                assert obligation is not None
                await self._dirty_status(
                    connection,
                    ReportingStatusScope.for_obligation(_obligation_from_row(obligation)),
                    "readability",
                    ReportingStatusEvidence(
                        "revision", reporting_revision_id, readable=existing[0]
                    ),
                    ReportingStatusEvidence("revision", reporting_revision_id, readable=readable),
                )

    # -- adjustments ------------------------------------------------------

    async def commit_adjustment(
        self, adjustment: ReportingAdjustmentRecord
    ) -> ReportingAdjustmentRecord:
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, adjustment.account_id)
            existing = await (
                await connection.execute(
                    f"SELECT {_ADJUSTMENT_COLUMNS} FROM reporting_adjustments"  # noqa: S608  # nosec B608
                    " WHERE reporting_adjustment_id = %s AND account_id = %s",
                    (adjustment.reporting_adjustment_id, adjustment.account_id),
                )
            ).fetchone()
            if existing is not None:
                stored = _adjustment_from_row(existing)
                if stored != adjustment:
                    raise LedgerConflictError(
                        "ADJUSTMENT_IMMUTABLE", "adjustment content is immutable"
                    )
                return stored
            revision = await (
                await connection.execute(
                    "SELECT finality, reporting_obligation_id FROM reporting_revisions"
                    " WHERE reporting_revision_id = %s AND account_id = %s",
                    (adjustment.adjusts_reporting_revision_id, adjustment.account_id),
                )
            ).fetchone()
            if revision is None:
                raise LedgerConflictError(
                    "REVISION_NOT_FOUND", "an adjustment must name a committed revision"
                )
            if revision[0] != "official":
                raise LedgerConflictError(
                    "ADJUSTMENT_REQUIRES_OFFICIAL",
                    "adjustments correct an official revision; restate a snapshot with a "
                    "superseding snapshot revision instead",
                )
            obligation = await (
                await connection.execute(
                    f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                    " WHERE reporting_obligation_id = %s AND account_id = %s",
                    (revision[1], adjustment.account_id),
                )
            ).fetchone()
            if obligation is None:
                raise LedgerConflictError(
                    "OBLIGATION_NOT_FOUND", "the adjustment target has no obligation"
                )
            await self._require_writable_generation(
                connection, _obligation_from_row(obligation).generation_key
            )
            validate_adjustment_currency(_obligation_from_row(obligation), adjustment)
            inserted = await (
                await connection.execute(
                    "INSERT INTO reporting_adjustments"
                    " (reporting_adjustment_id, account_id, adjusts_reporting_revision_id,"
                    "  reason_code, reason_detail, accounting_period_start,"
                    "  accounting_period_end, control_total_deltas, correction_observed_at,"
                    "  created_at, managed_control_total_deltas)"
                    " VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s::jsonb)"
                    " ON CONFLICT (reporting_adjustment_id) DO NOTHING"
                    " RETURNING reporting_adjustment_id",
                    (
                        adjustment.reporting_adjustment_id,
                        adjustment.account_id,
                        adjustment.adjusts_reporting_revision_id,
                        adjustment.reason_code,
                        adjustment.reason_detail,
                        adjustment.accounting_period_start,
                        adjustment.accounting_period_end,
                        _json([[name, value] for name, value in adjustment.control_total_deltas]),
                        adjustment.correction_observed_at,
                        adjustment.created_at,
                        (
                            _json(
                                [item.to_wire() for item in adjustment.managed_control_total_deltas]
                            )
                            if adjustment.managed_control_total_deltas is not None
                            else None
                        ),
                    ),
                )
            ).fetchone()
            if inserted is None:
                raise LedgerConflictError("ADJUSTMENT_UNAVAILABLE", "adjustment is unavailable")
            await self._append_change(
                connection,
                adjustment.account_id,
                "adjustment",
                adjustment.reporting_adjustment_id,
                consumer_id=_obligation_from_row(obligation).consumer_id,
            )
            if self._notifications_enabled:
                from adcp.reporting.ledger.notification_events import adjustment_event

                await self._record_notification(
                    connection,
                    adjustment_event(
                        adjustment,
                        await self._notification_now(connection),
                        consumer_id=_obligation_from_row(obligation).consumer_id,
                    ),
                )
                await self._dirty_status(
                    connection,
                    ReportingStatusScope.for_obligation(_obligation_from_row(obligation)),
                    "adjustment",
                    after=ReportingStatusEvidence("adjustment", adjustment.reporting_adjustment_id),
                )
        return adjustment

    async def list_adjustments(
        self, *, account_id: str, reporting_revision_ids: Sequence[str]
    ) -> tuple[ReportingAdjustmentRecord, ...]:
        if not reporting_revision_ids:
            return ()
        async with self._connection() as connection:
            rows = await (
                await connection.execute(
                    f"SELECT {_ADJUSTMENT_COLUMNS} FROM reporting_adjustments"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND adjusts_reporting_revision_id = ANY(%s)"
                    " ORDER BY created_at, reporting_adjustment_id",
                    (account_id, list(reporting_revision_ids)),
                )
            ).fetchall()
        return tuple(_adjustment_from_row(row) for row in rows)

    # -- consumer status (preview) ----------------------------------------

    async def record_consumer_status(
        self, status: ConsumerStatusRecord
    ) -> tuple[ConsumerStatusRecord, bool]:
        from adcp.reporting.evidence import consumer_reference

        consumer_reference(status.consumer_id)
        key = status.generation_key
        digest = _fingerprint(_consumer_status_payload(status))
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, status.account_id)
            replay = await self._replay(connection, status, digest)
            if replay is not None:
                return replay, False

            from adcp.reporting.ledger.status_snapshot import (
                read_snapshot_on,
                validate_status_evidence,
            )

            validate_status_evidence(
                status,
                await read_snapshot_on(
                    connection,
                    account_id=status.account_id,
                    clock=self._clock,
                    include_issue_scopes=await self._issue_scope_storage_on(connection),
                ),
            )

            leaf = await (
                await connection.execute(
                    "SELECT reporting_status_id FROM reporting_consumer_statuses"
                    " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                    "   AND delivery_config_version = %s AND report_definition_id = %s"
                    "   AND period_start = %s AND period_end = %s AND superseded = FALSE",
                    (
                        key.account_id,
                        status.consumer_id,
                        key.delivery_config_id,
                        key.delivery_config_version,
                        status.report_definition_id,
                        status.period_start,
                        status.period_end,
                    ),
                )
            ).fetchone()
            if status.supersedes_reporting_status_id:
                if leaf is None or leaf[0] != status.supersedes_reporting_status_id:
                    raise LedgerConflictError(
                        "STATUS_SUPERSEDES_STALE",
                        "supersedes_reporting_status_id must name this chain's current leaf; "
                        "a stale pointer would let a successful retry erase a recorded outage",
                    )
                await connection.execute(
                    "UPDATE reporting_consumer_statuses SET superseded = TRUE"
                    " WHERE account_id = %s AND consumer_id = %s AND reporting_status_id = %s",
                    (key.account_id, status.consumer_id, status.supersedes_reporting_status_id),
                )
            elif leaf is not None:
                raise LedgerConflictError(
                    "STATUS_SUPERSEDES_REQUIRED",
                    "this chain already has a current statement; a new statement must "
                    "explicitly supersede it",
                )
            try:
                await connection.execute(
                    "INSERT INTO reporting_consumer_statuses"
                    " (reporting_status_id, account_id, consumer_id, delivery_config_id,"
                    "  delivery_config_version, report_definition_id, period_start, period_end,"
                    "  period_source_timezone, consumer_status, status_as_of, recorded_at,"
                    "  supersedes_reporting_status_id, reporting_obligation_id,"
                    "  reporting_revision_id, observed_revision_content_sha256, failure_code,"
                    "  mismatch_code, consumer_commit_ref, seller_ledger_snapshot_id,"
                    "  seller_ledger_as_of, superseded, content_sha256)"
                    " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,"
                    "         %s, %s, %s, %s, %s, FALSE, %s)",
                    (
                        status.reporting_status_id,
                        status.account_id,
                        status.consumer_id,
                        status.delivery_config_id,
                        status.delivery_config_version,
                        status.report_definition_id,
                        status.period_start,
                        status.period_end,
                        status.period_source_timezone,
                        status.consumer_status,
                        status.status_as_of,
                        status.recorded_at,
                        status.supersedes_reporting_status_id,
                        status.reporting_obligation_id,
                        status.reporting_revision_id,
                        status.observed_revision_content_sha256,
                        status.failure_code,
                        status.mismatch_code,
                        status.consumer_commit_ref,
                        status.seller_ledger_snapshot_id,
                        status.seller_ledger_as_of,
                        digest,
                    ),
                )
            except Exception as error:
                raise _translate_integrity_error(error) from error
            await self._append_change(
                connection,
                status.account_id,
                "consumer_status",
                status.reporting_status_id,
                consumer_id=status.consumer_id,
            )
            from adcp.reporting.ledger.status_snapshot import settle_snapshot_on

            await settle_snapshot_on(self, connection, account_id=status.account_id)
            if self._notifications_enabled:
                await self._dirty_status(
                    connection,
                    ReportingStatusScope(
                        status.account_id,
                        status.generation_key,
                        status.reporting_obligation_id,
                        status.consumer_id,
                    ),
                    "consumer_status",
                    ReportingStatusEvidence("consumer_status", leaf[0]) if leaf else None,
                    ReportingStatusEvidence(
                        "consumer_status",
                        status.reporting_status_id,
                        supersedes_id=status.supersedes_reporting_status_id,
                    ),
                )
        return status, True

    async def record_consumer_status_with_lifecycle(
        self, status: ConsumerStatusRecord
    ) -> tuple[ConsumerStatusRecord, bool]:
        """Optional participant: the record and lifecycle share the source transaction."""
        return await self.record_consumer_status(status)

    async def read_status_snapshot(self, *, caller: ReportingCaller) -> ReportingStatusSnapshot:
        from adcp.reporting.ledger.delivery_models import ReportingDeliveryPrincipal
        from adcp.reporting.ledger.status_snapshot import settle_snapshot_on
        from adcp.reporting.materializer.capture import private_snapshot

        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, caller.account_id)
            return private_snapshot(
                await settle_snapshot_on(self, connection, account_id=caller.account_id),
                ReportingDeliveryPrincipal(caller.account_id, caller.consumer_id),
            )

    @staticmethod
    async def _get_consumer_status(
        connection: Any, status: ConsumerStatusRecord
    ) -> ConsumerStatusRecord | None:
        row = await (
            await connection.execute(
                # Bare columns: this query has no `s` alias to qualify against.
                f"SELECT {_STATUS_COLUMNS_BARE} FROM reporting_consumer_statuses"  # noqa: S608  # nosec B608
                " WHERE reporting_status_id = %s AND account_id = %s AND consumer_id = %s",
                (status.reporting_status_id, status.account_id, status.consumer_id),
            )
        ).fetchone()
        return _status_from_row(row) if row else None

    async def resolve_consumer_status_replay(
        self, status: ConsumerStatusRecord
    ) -> ConsumerStatusRecord | None:
        async with self._connection() as connection:
            return await self._replay(
                connection, status, _fingerprint(_consumer_status_payload(status))
            )

    async def _replay(
        self, connection: Any, status: ConsumerStatusRecord, digest: str
    ) -> ConsumerStatusRecord | None:
        existing = await (
            await connection.execute(
                "SELECT content_sha256 FROM reporting_consumer_statuses"
                " WHERE reporting_status_id = %s AND account_id = %s AND consumer_id = %s",
                (status.reporting_status_id, status.account_id, status.consumer_id),
            )
        ).fetchone()
        if existing is None:
            return None
        if existing[0] != digest:
            raise LedgerConflictError(
                "STATUS_IDENTITY_CONFLICT",
                f"reporting_status_id {status.reporting_status_id} was already "
                "recorded with different content",
            )
        stored = await self._get_consumer_status(connection, status)
        assert stored is not None
        return stored

    async def list_consumer_statuses(
        self,
        *,
        account_id: str,
        consumer_id: str,
        reporting_obligation_ids: Sequence[str] | None = None,
    ) -> tuple[ConsumerStatusRecord, ...]:
        # Attaches by obligation id *or* by the exact logical period key, so a
        # chain filed before the obligation existed is not lost, forked, or
        # reset when the seller later repairs the missing obligation.
        clause = ""
        params: list[Any] = [account_id, consumer_id]
        if reporting_obligation_ids is not None:
            clause = (
                " AND EXISTS ("
                "   SELECT 1 FROM reporting_obligations o"
                "   WHERE o.reporting_obligation_id = ANY(%s)"
                "     AND o.account_id = s.account_id"
                "     AND (o.reporting_obligation_id = s.reporting_obligation_id OR ("
                "         o.delivery_config_id = s.delivery_config_id"
                "     AND o.delivery_config_version = s.delivery_config_version"
                "     AND o.report_definition_id = s.report_definition_id"
                "     AND o.period_start = s.period_start"
                "     AND o.period_end = s.period_end)))"
            )
            params.append(list(reporting_obligation_ids))
        async with self._connection() as connection:
            rows = await (
                await connection.execute(
                    f"SELECT {_STATUS_COLUMNS} FROM reporting_consumer_statuses s"  # noqa: S608  # nosec B608
                    f" WHERE s.account_id = %s AND s.consumer_id = %s{clause}"
                    " ORDER BY s.recorded_at, s.reporting_status_id",
                    tuple(params),
                )
            ).fetchall()
        return tuple(_status_from_row(row) for row in rows)

    # -- issue lifecycle --------------------------------------------------

    async def ensure_issue_opened(
        self,
        *,
        issue_key: str,
        account_id: str,
        consumer_id: str | None,
        observed_at: datetime,
        status_scope: ReportingStatusScope | None = None,
    ) -> ReportingIssueLifecycle:
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, account_id)
            return await self._ensure_issue_opened_on(
                connection,
                issue_key=issue_key,
                account_id=account_id,
                consumer_id=consumer_id,
                observed_at=observed_at,
                status_scope=status_scope,
            )

    async def _ensure_issue_opened_on(
        self,
        connection: Any,
        *,
        issue_key: str,
        account_id: str,
        consumer_id: str | None,
        observed_at: datetime,
        status_scope: ReportingStatusScope | None = None,
        enqueue: bool = True,
    ) -> ReportingIssueLifecycle:
        live = await self._live_issue(connection, issue_key, account_id)
        if live is not None:
            if live.consumer_id != consumer_id:
                raise ReportingNotificationError("invalid_status_scope")
            await self._dirty_issue(connection, live, status_scope, live, enqueue=enqueue)
            return live
        row = await (
            await connection.execute(
                "SELECT generation, consumer_id, issue_id FROM reporting_issue_lifecycle"
                " WHERE account_id = %s AND issue_key = %s ORDER BY generation DESC LIMIT 1",
                (account_id, issue_key),
            )
        ).fetchone()
        if row is not None:
            if row[1] != consumer_id:
                raise ReportingNotificationError("invalid_status_scope")
            previous = (
                await (
                    await connection.execute(
                        "SELECT scope FROM reporting_issue_status_scopes"
                        " WHERE account_id=%s AND issue_id=%s",
                        (account_id, row[2]),
                    )
                ).fetchone()
                if await self._issue_scope_storage_on(connection)
                else None
            )
            previous_scope = decode_status_scope(previous[0]) if previous else None
            if status_scope is not None:
                validate_scope_refinement(previous_scope, status_scope)
            else:
                status_scope = previous_scope
        generation = int(row[0] if row else 0) + 1
        issue_id = issue_id_for_occurrence(issue_key, generation)
        await connection.execute(
            "INSERT INTO reporting_issue_lifecycle"
            " (issue_key, account_id, generation, issue_id, consumer_id, opened_at, issue_state)"
            " VALUES (%s, %s, %s, %s, %s, %s, 'open')",
            (issue_key, account_id, generation, issue_id, consumer_id, _utc(observed_at)),
        )
        record = ReportingIssueLifecycle(
            issue_key=issue_key,
            issue_id=issue_id,
            account_id=account_id,
            consumer_id=consumer_id,
            opened_at=_utc(observed_at),
            generation=generation,
        )
        await self._dirty_issue(connection, record, status_scope, enqueue=enqueue)
        return record

    async def set_issue_state(
        self,
        *,
        issue_key: str,
        account_id: str,
        state: Literal["acknowledged", "waived"],
        at: datetime,
        external_ref: str | None = None,
        status_scope: ReportingStatusScope | None = None,
    ) -> ReportingIssueLifecycle:
        if state not in {"acknowledged", "waived"}:
            raise LedgerConflictError(
                "ISSUE_STATE_NOT_OPERATOR_SETTABLE",
                f"issue_state {state!r} is not settable by an operator. 'resolved' is "
                "reachable only when the condition actually clears -- the projection "
                "retires it -- because a seller must not retire a mismatch out of a "
                "degraded projection while the statement that caused it is still the "
                "consumer's current leaf",
            )
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, account_id)
            live = await self._live_issue(connection, issue_key, account_id)
            if live is None:
                raise LedgerConflictError(
                    "ISSUE_NOT_OPEN",
                    f"no open issue {issue_key!r} for this account; a retired issue cannot be "
                    "reopened, and a recurrence gets a new occurrence",
                )
            check_issue_state_transition(live.issue_state, state)
            if state == "waived" and live.issue_state != "waived":
                from adcp.reporting.ledger.status_projection import bind_mismatch_waiver
                from adcp.reporting.ledger.status_snapshot import read_snapshot_on

                snapshot = await read_snapshot_on(
                    connection,
                    account_id=account_id,
                    as_of=_utc(at),
                    include_issue_scopes=await self._issue_scope_storage_on(connection),
                )
                live = bind_mismatch_waiver(snapshot, live)
                await _save_waiver_binding(connection, live)
            waived_at = (
                live.retired_at
                if live.waived_reporting_status_id is not None and live.retired_at is not None
                else _utc(at)
            )
            await connection.execute(
                "UPDATE reporting_issue_lifecycle"
                " SET issue_state = %s,"
                "     external_ref = COALESCE(%s, external_ref),"
                "     retired_at = CASE WHEN %s = 'waived' THEN %s ELSE retired_at END"
                " WHERE account_id = %s AND issue_key = %s AND generation = %s",
                (
                    state,
                    external_ref,
                    state,
                    waived_at,
                    account_id,
                    issue_key,
                    live.generation,
                ),
            )
            refreshed = await self._issue_row(connection, issue_key, account_id, live.generation)
            assert refreshed is not None
            # Derive the no-op from the resulting row rather than predicting
            # it: an idempotent re-acknowledge changes nothing and enqueues
            # nothing, while anything that does move retained evidence stays
            # reconstructable for the projector.
            await self._dirty_issue(connection, refreshed, status_scope, live)
            return refreshed

    async def retire_issue(
        self,
        *,
        issue_key: str,
        account_id: str,
        at: datetime,
        status_scope: ReportingStatusScope | None = None,
    ) -> ReportingIssueLifecycle | None:
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, account_id)
            return await self._retire_issue_on(
                connection,
                issue_key=issue_key,
                account_id=account_id,
                at=at,
                status_scope=status_scope,
            )

    async def _retire_issue_on(
        self,
        connection: Any,
        *,
        issue_key: str,
        account_id: str,
        at: datetime,
        status_scope: ReportingStatusScope | None = None,
        enqueue: bool = True,
    ) -> ReportingIssueLifecycle | None:
        live = await self._live_issue(connection, issue_key, account_id)
        if live is None or not issue_is_retirable(live.issue_state):
            return None
        if live.issue_state != "waived":
            check_issue_state_transition(live.issue_state, "resolved")
        await connection.execute(
            "UPDATE reporting_issue_lifecycle SET issue_state = 'resolved', retired_at = %s"
            " WHERE account_id = %s AND issue_key = %s AND generation = %s",
            (_utc(at), account_id, issue_key, live.generation),
        )
        retired = await self._issue_row(connection, issue_key, account_id, live.generation)
        assert retired is not None
        await self._dirty_issue(connection, retired, status_scope, live, enqueue=enqueue)
        return retired

    async def get_issue(self, *, issue_key: str, account_id: str) -> ReportingIssueLifecycle | None:
        async with self._connection() as connection:
            return await self._live_issue(connection, issue_key, account_id)

    @staticmethod
    async def _live_issue(
        connection: Any, issue_key: str, account_id: str
    ) -> ReportingIssueLifecycle | None:
        row = await (
            await connection.execute(
                f"SELECT {_ISSUE_COLUMNS} FROM reporting_issue_lifecycle"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND issue_key = %s"
                "   AND issue_state IN ('open', 'acknowledged', 'waived')",
                (account_id, issue_key),
            )
        ).fetchone()
        return await _with_waiver_binding(connection, _issue_from_row(row)) if row else None

    @staticmethod
    async def _issue_row(
        connection: Any, issue_key: str, account_id: str, generation: int
    ) -> ReportingIssueLifecycle | None:
        row = await (
            await connection.execute(
                f"SELECT {_ISSUE_COLUMNS} FROM reporting_issue_lifecycle"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND issue_key = %s AND generation = %s",
                (account_id, issue_key, generation),
            )
        ).fetchone()
        return await _with_waiver_binding(connection, _issue_from_row(row)) if row else None

    # -- snapshots --------------------------------------------------------

    async def open_snapshot(
        self, *, caller: ReportingCaller, filters_fingerprint: str
    ) -> LedgerSnapshot:
        account_id = caller.account_id
        async with self._connection() as connection:
            row = await (
                await connection.execute(
                    "SELECT COALESCE(MAX(seq), 0), now() FROM reporting_ledger_changes"
                    " WHERE account_id=%s AND consumer_id=%s AND record_kind IN"
                    " ('obligation','revision','adjustment','consumer_status')",
                    (account_id, caller.consumer_id),
                )
            ).fetchone()
        assert row is not None
        max_sequence = int(row[0])
        as_of = _utc(self._clock()) if self._clock is not None else _utc(row[1])
        return LedgerSnapshot(
            snapshot_id="rpls_"
            + _fingerprint([account_id, caller.consumer_id, filters_fingerprint, max_sequence])[
                :32
            ],
            account_id=account_id,
            consumer_id=caller.consumer_id,
            ledger_as_of=as_of,
            max_sequence=max_sequence,
        )

    async def read_page(
        self,
        *,
        snapshot: LedgerSnapshot,
        consumer_id: str | None,
        delivery_config_ids: Sequence[str] | None,
        media_buy_ids: Sequence[str] | None,
        offset: int,
        limit: int,
        changes_after_sequence: int | None,
        feed_purposes: Sequence[str] | None = None,
        period_start: datetime | None = None,
        period_end: datetime | None = None,
    ) -> LedgerPage:
        if consumer_id not in {None, snapshot.consumer_id}:
            raise LedgerConflictError(
                "CURSOR_SNAPSHOT_MISMATCH", "snapshot belongs to another caller"
            )
        consumer_id = snapshot.consumer_id
        lower = changes_after_sequence or 0
        async with self._connection() as connection:
            rows = await (
                await connection.execute(
                    "SELECT seq, record_kind, record_id FROM reporting_ledger_changes"
                    " WHERE account_id = %s AND consumer_id=%s AND seq > %s AND seq <= %s"
                    " ORDER BY seq",
                    (snapshot.account_id, consumer_id, lower, snapshot.max_sequence),
                )
            ).fetchall()

            selected: list[tuple[int, LedgerRecordKind, Any]] = []
            for _seq, kind, record_id in rows:
                record = await self._resolve(
                    connection, snapshot.account_id, kind, record_id, consumer_id=consumer_id
                )
                if record is None:
                    continue
                if not await self._in_scope(
                    connection,
                    kind,
                    record,
                    delivery_config_ids,
                    media_buy_ids,
                    consumer_id,
                    feed_purposes,
                    period_start,
                    period_end,
                ):
                    continue
                selected.append((_seq, kind, record))

        window = selected[offset : offset + limit]
        has_more = offset + limit < len(selected)
        return LedgerPage(
            obligations=tuple(item[2] for item in window if item[1] == "obligation"),
            revisions=tuple(item[2] for item in window if item[1] == "revision"),
            adjustments=tuple(item[2] for item in window if item[1] == "adjustment"),
            consumer_statuses=tuple(item[2] for item in window if item[1] == "consumer_status"),
            total_count=len(selected),
            has_more=has_more,
            cursor=(
                encode_cursor({"snapshot": snapshot.snapshot_id, "offset": offset + limit})
                if has_more
                else None
            ),
        )

    async def _resolve(
        self,
        connection: Any,
        account_id: str,
        kind: str,
        record_id: str,
        *,
        consumer_id: str | None = None,
    ) -> Any | None:
        if kind not in _RESOLVERS:
            return None  # Higher-tier records are never projected by Core.
        table, columns, key, builder = _RESOLVERS[kind]
        consumer_filter = " AND consumer_id=%s" if kind == "consumer_status" else ""
        parameters = (
            (account_id, record_id, consumer_id) if consumer_filter else (account_id, record_id)
        )
        row = await (
            await connection.execute(
                f"SELECT {columns} FROM {table} WHERE account_id = %s AND {key} = %s"  # noqa: S608  # nosec B608
                f"{consumer_filter}",  # nosec B608
                parameters,
            )
        ).fetchone()
        return builder(row) if row else None

    async def _in_scope(
        self,
        connection: Any,
        kind: str,
        record: Any,
        delivery_config_ids: Sequence[str] | None,
        media_buy_ids: Sequence[str] | None,
        consumer_id: str | None,
        feed_purposes: Sequence[str] | None = None,
        period_start: datetime | None = None,
        period_end: datetime | None = None,
    ) -> bool:
        from adcp.reporting.ledger.status_projection import configuration_selected, period_selected

        if kind == "consumer_status":
            # A caller sees only its own statements; another consumer's
            # operational status is never disclosed.
            if consumer_id is None or record.consumer_id != consumer_id:
                return False
            owner = record
            start, end = record.period_start, record.period_end
        else:
            owner = await self._obligation_for(connection, kind, record)
            if owner is None or owner.consumer_id != consumer_id:
                return False
            start, end = owner.period.start, owner.period.end
            if media_buy_ids and not set(media_buy_ids).intersection(owner.media_buy_ids):
                return False
        configurations = await self._list_configurations_on(
            connection,
            account_id=record.account_id,
            consumer_id=owner.consumer_id,
            delivery_config_ids=[owner.delivery_config_id],
        )
        configuration = next(
            (c for c in configurations if c.generation_key == owner.generation_key), None
        )
        if configuration is None:
            return False
        return configuration_selected(
            configuration,
            delivery_config_ids=delivery_config_ids or (),
            media_buy_ids=media_buy_ids or (),
            feed_purposes=feed_purposes or (),
        ) and period_selected(start, end, period_start, period_end)

    async def _obligation_for(
        self, connection: Any, kind: str, record: Any
    ) -> ReportingObligationRecord | None:
        if kind == "obligation":
            found: ReportingObligationRecord = record
            return found
        if kind == "revision":
            return await self._resolve(
                connection, record.account_id, "obligation", record.reporting_obligation_id
            )
        revision = await self._resolve(
            connection, record.account_id, "revision", record.adjusts_reporting_revision_id
        )
        if revision is None:
            return None
        return await self._resolve(
            connection, revision.account_id, "obligation", revision.reporting_obligation_id
        )

    # -- leasing ----------------------------------------------------------

    async def _period_close_generations_on(
        self, connection: Any, identity: tuple[str, str, str, int]
    ) -> list[tuple[str, str, int]]:
        # A base-tier store may share a schema installed by another participant.
        # Probe on this transaction without caching schema presence or validating
        # every catalog object on the lease path. Call only after the account lock.
        present = await (
            await connection.execute(
                "SELECT to_regclass('reporting_materializer_candidates') IS NOT NULL"
            )
        ).fetchone()
        if not present[0]:
            return []
        rows = await (
            await connection.execute(
                "SELECT consumer_id,reporting_obligation_id,generation"
                " FROM reporting_materializer_candidates WHERE account_id=%s"
                " AND consumer_id=%s AND delivery_config_id=%s AND delivery_config_version=%s",
                identity,
            )
        ).fetchall()
        return [(row[0], row[1], row[2]) for row in rows]

    async def _restore_period_close_generations_on(
        self,
        connection: Any,
        identity: tuple[str, str, str, int],
        snapshot: list[tuple[str, str, int]],
    ) -> None:
        # These SDK writes change only lease fields. All supported source writers
        # hold the same account lock, so no source change can intervene. Restore
        # only the captured generation plus the known single trigger increment;
        # never decrement blindly if the trigger was disabled or did not fire.
        # Unexpected generation changes remain fenced for schema/target validation.
        # Wakeups and due_at/reason retain the production tier's existing behavior.
        if not snapshot:
            return
        await connection.execute(
            "UPDATE reporting_materializer_candidates c SET generation=s.generation"
            " FROM unnest(%s::text[],%s::text[],%s::bigint[])"
            " AS s(consumer_id,reporting_obligation_id,generation)"
            " WHERE c.account_id=%s AND c.consumer_id=%s AND c.delivery_config_id=%s"
            " AND c.delivery_config_version=%s AND c.consumer_id=s.consumer_id"
            " AND c.reporting_obligation_id=s.reporting_obligation_id"
            " AND c.generation=s.generation+1",
            (
                [row[0] for row in snapshot],
                [row[1] for row in snapshot],
                [row[2] for row in snapshot],
                *identity,
            ),
        )

    async def lease_period_close(
        self, *, worker_id: str, now: datetime, lease_seconds: float
    ) -> LeasedConfiguration | None:
        from psycopg.errors import LockNotAvailable
        from psycopg.pq import TransactionStatus

        moment = _utc(now)
        expires = moment + timedelta(seconds=lease_seconds)
        after = self._period_close_sample
        wait_until = None
        following = None
        result = None
        async with self._connection() as connection:
            # Never add a blocking account edge while a caller transaction may
            # already own locks. Standalone turns may queue only before the first
            # account acquisition, including acquisitions that find a stale row.
            can_wait = connection.info.transaction_status == TransactionStatus.IDLE
            async with connection.transaction():
                await self._bound_lock_waits(connection)
                # A materializer installed by any participant adds an AFTER UPDATE
                # trigger taking the account lock. Sample without row locks, then take
                # that account lock before locking a configuration. The base store can
                # share that schema, so the ordering belongs here, not only on a tier.
                for _ in range(2):
                    continuation = (
                        " AND (COALESCE(t.lease_turn,0),"
                        " COALESCE(c.lease_expires_at,'-infinity'::timestamptz),"
                        " c.account_id,c.consumer_id,c.delivery_config_id,c.delivery_confi"
                        "g_version)"
                        " > (%s,COALESCE(%s::timestamptz,'-infinity'::timestamptz),%s,%s,%s,%s)"
                        if after is not None
                        else ""
                    )
                    query = (
                        "SELECT c.account_id,c.consumer_id,c.delivery_config_id,c.delivery"
                        "_config_version,"  # nosec B608
                        " COALESCE(t.lease_turn,0),c.lease_expires_at FROM"
                        " reporting_configurations c"
                        " LEFT JOIN adcp_reporting_configuration_lease_turns t"
                        " ON (t.account_id,t.consumer_id,t.delivery_config_id,t.delivery_c"
                        "onfig_version)"
                        " = (c.account_id,c.consumer_id,c.delivery_config_id,c.delivery_co"
                        "nfig_version)"
                        " WHERE NOT c.quarantined AND (c.lease_expires_at IS NULL OR c.lea"
                        "se_expires_at<=%s)"
                        + continuation
                        + " ORDER BY COALESCE(t.lease_turn,0),c.lease_expires_at NULLS FIRST,"
                        " c.account_id,c.consumer_id,c.delivery_config_id,c.delivery_confi"
                        "g_version LIMIT 32"
                    )
                    rows = await (
                        await connection.execute(query, (moment, *(after or ())))
                    ).fetchall()
                    if rows or after is None:
                        break
                    after = None
                for candidate in rows:
                    row = candidate[:4]
                    following = (candidate[4], candidate[5], *row)
                    locked = await (
                        await connection.execute(
                            "SELECT pg_try_advisory_xact_lock(hashtext('adcp.reporting:' || %s))",
                            (row[0],),
                        )
                    ).fetchone()
                    if not locked[0]:
                        if not can_wait:
                            continue
                        # A try-lock alone can starve behind a continuous queue of
                        # ordinary writers, even though each writer commits promptly.
                        # Join that queue briefly, sharing one wait budget per turn.
                        # The savepoint rolls back a timed-out wait and its SET LOCAL;
                        # successful acquisition restores the caller's lock timeout.
                        if wait_until is None:
                            wait_until = monotonic() + _LEASE_ACCOUNT_WAIT_SECONDS
                        remaining_ms = int((wait_until - monotonic()) * 1000)
                        if remaining_ms <= 0:
                            continue
                        try:
                            async with connection.transaction():
                                previous, configured_ms = await (
                                    await connection.execute(
                                        "SELECT current_setting('lock_timeout'),setting::integer"
                                        " FROM pg_settings WHERE name='lock_timeout'"
                                    )
                                ).fetchone()
                                limit_ms = min(remaining_ms, configured_ms or remaining_ms)
                                await connection.execute(
                                    "SELECT set_config('lock_timeout',%s,true)", (f"{limit_ms}ms",)
                                )
                                await connection.execute(
                                    "SELECT pg_advisory_xact_lock(hashtext('adcp.reporting:' ||"
                                    " %s))",
                                    (row[0],),
                                )
                                await connection.execute(
                                    "SELECT set_config('lock_timeout',%s,true)", (previous,)
                                )
                        except LockNotAvailable:
                            continue
                    can_wait = False
                    snapshot = await self._period_close_generations_on(connection, tuple(row))
                    acquired = await (
                        await connection.execute(
                            "UPDATE reporting_configurations SET lease_worker_id = %s,"
                            " lease_expires_at = %s"
                            " WHERE (account_id,consumer_id,delivery_config_id,delivery_config"
                            "_version) = ("
                            " SELECT c.account_id,c.consumer_id,c.delivery_config_id,c.deliver"
                            "y_config_version"
                            " FROM reporting_configurations c WHERE c.account_id=%s"
                            " AND c.consumer_id=%s AND c.delivery_config_id=%s AND c.delivery_"
                            "config_version=%s"
                            " AND NOT c.quarantined AND (c.lease_expires_at IS NULL OR c.lease"
                            "_expires_at<=%s)"
                            " FOR UPDATE OF c SKIP LOCKED) RETURNING account_id",
                            (worker_id, expires, *row, moment),
                        )
                    ).fetchone()
                    if acquired is None:
                        continue
                    await self._restore_period_close_generations_on(
                        connection, tuple(row), snapshot
                    )
                    await connection.execute(
                        "INSERT INTO adcp_reporting_configuration_lease_turns"
                        " (account_id,consumer_id,delivery_config_id,delivery_config_versi"
                        "on,lease_turn)"
                        " VALUES(%s,%s,%s,%s,nextval('adcp_reporting_configuration_lease_t"
                        "urn_seq'))"
                        " ON CONFLICT(account_id,consumer_id,delivery_config_id,delivery_c"
                        "onfig_version)"
                        " DO UPDATE SET"
                        " lease_turn=nextval('adcp_reporting_configuration_lease_turn_seq')",
                        tuple(row),
                    )
                    result = LeasedConfiguration(row[0], row[1], row[2], row[3], expires)
                    break
        # Only sampling uses this hint: leases and fairness ranks stay transactional.
        # Continue past a busy prefix on the next turn; a successful turn returns to
        # durable fairness order. Empty tails wrap at most once without row locks.
        self._period_close_sample = following if result is None else None
        return result

    async def release_period_close(self, lease: LeasedConfiguration, *, worker_id: str) -> None:
        key = lease.generation_key
        identity = (
            key.account_id,
            key.consumer_id,
            key.delivery_config_id,
            key.delivery_config_version,
        )
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, key.account_id)
            snapshot = await self._period_close_generations_on(connection, identity)
            released = await (
                await connection.execute(
                    "UPDATE reporting_configurations SET lease_worker_id = NULL,"
                    " lease_expires_at = NULL"
                    " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                    " AND delivery_config_version = %s AND lease_worker_id = %s"
                    " AND lease_expires_at = %s RETURNING account_id",
                    (*identity, worker_id, lease.lease_expires_at),
                )
            ).fetchone()
            if released is not None:
                await self._restore_period_close_generations_on(connection, identity, snapshot)

Durable reporting ledger over a caller-supplied connection pool.

Subclasses

Class variables

var is_durable : ClassVar[bool]

Methods

async def commit_adjustment(self,
adjustment: ReportingAdjustmentRecord) ‑> ReportingAdjustmentRecord
Expand source code
async def commit_adjustment(
    self, adjustment: ReportingAdjustmentRecord
) -> ReportingAdjustmentRecord:
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, adjustment.account_id)
        existing = await (
            await connection.execute(
                f"SELECT {_ADJUSTMENT_COLUMNS} FROM reporting_adjustments"  # noqa: S608  # nosec B608
                " WHERE reporting_adjustment_id = %s AND account_id = %s",
                (adjustment.reporting_adjustment_id, adjustment.account_id),
            )
        ).fetchone()
        if existing is not None:
            stored = _adjustment_from_row(existing)
            if stored != adjustment:
                raise LedgerConflictError(
                    "ADJUSTMENT_IMMUTABLE", "adjustment content is immutable"
                )
            return stored
        revision = await (
            await connection.execute(
                "SELECT finality, reporting_obligation_id FROM reporting_revisions"
                " WHERE reporting_revision_id = %s AND account_id = %s",
                (adjustment.adjusts_reporting_revision_id, adjustment.account_id),
            )
        ).fetchone()
        if revision is None:
            raise LedgerConflictError(
                "REVISION_NOT_FOUND", "an adjustment must name a committed revision"
            )
        if revision[0] != "official":
            raise LedgerConflictError(
                "ADJUSTMENT_REQUIRES_OFFICIAL",
                "adjustments correct an official revision; restate a snapshot with a "
                "superseding snapshot revision instead",
            )
        obligation = await (
            await connection.execute(
                f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                " WHERE reporting_obligation_id = %s AND account_id = %s",
                (revision[1], adjustment.account_id),
            )
        ).fetchone()
        if obligation is None:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND", "the adjustment target has no obligation"
            )
        await self._require_writable_generation(
            connection, _obligation_from_row(obligation).generation_key
        )
        validate_adjustment_currency(_obligation_from_row(obligation), adjustment)
        inserted = await (
            await connection.execute(
                "INSERT INTO reporting_adjustments"
                " (reporting_adjustment_id, account_id, adjusts_reporting_revision_id,"
                "  reason_code, reason_detail, accounting_period_start,"
                "  accounting_period_end, control_total_deltas, correction_observed_at,"
                "  created_at, managed_control_total_deltas)"
                " VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s::jsonb)"
                " ON CONFLICT (reporting_adjustment_id) DO NOTHING"
                " RETURNING reporting_adjustment_id",
                (
                    adjustment.reporting_adjustment_id,
                    adjustment.account_id,
                    adjustment.adjusts_reporting_revision_id,
                    adjustment.reason_code,
                    adjustment.reason_detail,
                    adjustment.accounting_period_start,
                    adjustment.accounting_period_end,
                    _json([[name, value] for name, value in adjustment.control_total_deltas]),
                    adjustment.correction_observed_at,
                    adjustment.created_at,
                    (
                        _json(
                            [item.to_wire() for item in adjustment.managed_control_total_deltas]
                        )
                        if adjustment.managed_control_total_deltas is not None
                        else None
                    ),
                ),
            )
        ).fetchone()
        if inserted is None:
            raise LedgerConflictError("ADJUSTMENT_UNAVAILABLE", "adjustment is unavailable")
        await self._append_change(
            connection,
            adjustment.account_id,
            "adjustment",
            adjustment.reporting_adjustment_id,
            consumer_id=_obligation_from_row(obligation).consumer_id,
        )
        if self._notifications_enabled:
            from adcp.reporting.ledger.notification_events import adjustment_event

            await self._record_notification(
                connection,
                adjustment_event(
                    adjustment,
                    await self._notification_now(connection),
                    consumer_id=_obligation_from_row(obligation).consumer_id,
                ),
            )
            await self._dirty_status(
                connection,
                ReportingStatusScope.for_obligation(_obligation_from_row(obligation)),
                "adjustment",
                after=ReportingStatusEvidence("adjustment", adjustment.reporting_adjustment_id),
            )
    return adjustment
async def commit_obligation(self,
obligation: ReportingObligationRecord) ‑> ReportingObligationRecord
Expand source code
async def commit_obligation(
    self, obligation: ReportingObligationRecord
) -> ReportingObligationRecord:
    key = obligation.generation_key
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, key.account_id)
        await self._require_writable_generation(connection, key)
        existing_row = await (
            await connection.execute(
                f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                " AND delivery_config_version = %s AND period_start = %s AND period_end = %s",
                (
                    key.account_id,
                    key.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                    obligation.period.start,
                    obligation.period.end,
                ),
            )
        ).fetchone()
        if existing_row is not None:
            return _obligation_from_row(existing_row)
        require_frozen_currency(obligation.currency)
        try:
            inserted = await (
                await connection.execute(
                    "INSERT INTO reporting_obligations"
                    " (reporting_obligation_id, account_id, delivery_config_id,"
                    "  delivery_config_version, report_definition_id, reporting_profile,"
                    "  feed_purpose, period_key, period_start, period_end, source_timezone,"
                    "  expected_at, scope_resolved_at, automated_recovery_deadline_at,"
                    "  required_finality, coverage_status, media_buy_ids, package_ids,"
                    "  schedule, definition, created_at, currency, consumer_id)"
                    " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,"
                    "         %s::jsonb, %s::jsonb, %s::jsonb, %s::jsonb, %s, %s, %s)"
                    " ON CONFLICT (account_id, consumer_id, delivery_config_id, delive"
                    "ry_config_version,"
                    "              period_start, period_end) DO NOTHING"
                    " RETURNING reporting_obligation_id",
                    (
                        obligation.reporting_obligation_id,
                        key.account_id,
                        key.delivery_config_id,
                        key.delivery_config_version,
                        obligation.report_definition_id,
                        obligation.reporting_profile,
                        obligation.feed_purpose,
                        obligation.period.period_key,
                        obligation.period.start,
                        obligation.period.end,
                        obligation.period.source_timezone,
                        obligation.period.expected_at,
                        obligation.scope_resolved_at,
                        obligation.automated_recovery_deadline_at,
                        obligation.required_finality,
                        obligation.coverage_status,
                        _json(sorted(obligation.media_buy_ids)),
                        _json(sorted(obligation.package_ids)),
                        _json(_schedule_payload(obligation.schedule)),
                        (
                            _json(_definition_payload(obligation.definition))
                            if obligation.definition
                            else None
                        ),
                        obligation.created_at,
                        obligation.currency,
                        obligation.consumer_id,
                    ),
                )
            ).fetchone()
        except Exception as error:
            raise _translate_integrity_error(error) from error
        if inserted is not None:
            await self._append_change(
                connection,
                obligation.account_id,
                "obligation",
                obligation.reporting_obligation_id,
                consumer_id=obligation.consumer_id,
            )
            if self._notifications_enabled:
                await self._dirty_status(
                    connection,
                    ReportingStatusScope.for_obligation(obligation),
                    "obligation",
                    after=ReportingStatusEvidence(
                        "obligation", obligation.reporting_obligation_id
                    ),
                )
            return obligation
    # Another worker won the period close; converge on its obligation.
    existing = await self.find_obligation(
        account_id=obligation.account_id,
        consumer_id=obligation.consumer_id,
        delivery_config_id=obligation.delivery_config_id,
        delivery_config_version=obligation.delivery_config_version,
        period_start=obligation.period.start,
        period_end=obligation.period.end,
    )
    if existing is None:  # pragma: no cover - only under concurrent deletion
        raise LedgerConflictError(
            "OBLIGATION_LOST", "the obligation vanished between insert and read"
        )
    return existing
async def commit_provisional_observation(self,
observation: ProvisionalObservation,
revision: ReportingRevisionRecord,
rows: Sequence[dict[str, Any]]) ‑> ReportingRevisionRecord
Expand source code
async def commit_provisional_observation(
    self,
    observation: ProvisionalObservation,
    revision: ReportingRevisionRecord,
    rows: Sequence[dict[str, Any]],
) -> ReportingRevisionRecord:
    acquisition = observation.acquisition
    key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
    if (
        revision.account_id != acquisition.account_id
        or revision.reporting_obligation_id != acquisition.obligation_id
        or revision.reporting_revision_id != observation.revision_id
    ):
        raise LedgerConflictError("OBSERVATION_CONFLICT", "observation identity differs")
    async with self.transaction(), self._connection() as connection:
        await self._lock_account(connection, acquisition.account_id)
        await self._require_provisional_schema(connection)
        existing = await (
            await connection.execute(
                "SELECT reporting_revision_id,payload FROM reporting_provisional_observations"
                " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                key,
            )
        ).fetchone()
        if existing is not None:
            retained_observation = ProvisionalObservation.from_wire(existing[1])
            if (
                retained_observation.acquisition != acquisition
                or existing[0] != observation.revision_id
            ):
                raise LedgerConflictError("OBSERVATION_CONFLICT", "observation replay differs")
            retained_revision = await self.get_revision(
                account_id=acquisition.account_id, reporting_revision_id=existing[0]
            )
            if retained_revision is None:
                raise LedgerConflictError(
                    "HISTORY_UNAVAILABLE", "observation revision is missing"
                )
            # Reuse the immutable publication replay checks while holding
            # the observation's account lock and transaction.
            return await self.commit_revision(revision, rows)
        reserved = await (
            await connection.execute(
                "SELECT payload FROM reporting_provisional_acquisitions"
                " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                key,
            )
        ).fetchone()
        if reserved is None or ProvisionalAcquisition.from_wire(reserved[0]) != acquisition:
            raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition was not reserved")
        checkpoint = await self.get_restatement_checkpoint(
            account_id=acquisition.account_id,
            reporting_obligation_id=acquisition.obligation_id,
        )
        if checkpoint is not None and checkpoint.next_observation != acquisition.ordinal:
            raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
        committed = await self.commit_revision(revision, rows)
        await self.record_restatement_checkpoint(
            RestatementCheckpoint(
                acquisition.account_id,
                acquisition.obligation_id,
                observation.checked_at,
                acquisition.ordinal + 1,
                observation.provisional_until,
            )
        )
        await connection.execute(
            "INSERT INTO reporting_provisional_observations"
            " (account_id,reporting_obligation_id,ordinal,reporting_revision_id,payload)"
            " VALUES (%s,%s,%s,%s,%s::jsonb)",
            (*key, revision.reporting_revision_id, _json(observation.to_wire())),
        )
        return committed
async def commit_revision(self,
revision: ReportingRevisionRecord,
rows: Sequence[dict[str, Any]]) ‑> ReportingRevisionRecord
Expand source code
async def commit_revision(
    self, revision: ReportingRevisionRecord, rows: Sequence[dict[str, Any]]
) -> ReportingRevisionRecord:
    rows = tuple(deepcopy(row) for row in rows)
    if revision.row_count != len(rows):
        raise LedgerConflictError(
            "ROW_COUNT_MISMATCH",
            f"revision declares {revision.row_count} rows but {len(rows)} were supplied",
        )
    validate_managed_revision_rows(revision, rows)
    digest = _fingerprint(_revision_payload(revision))
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, revision.account_id)
        existing = await (
            await connection.execute(
                f"SELECT {_REVISION_COLUMNS}, content_sha256 FROM reporting_revisions"  # noqa: S608  # nosec B608
                " WHERE reporting_revision_id = %s AND account_id = %s",
                (revision.reporting_revision_id, revision.account_id),
            )
        ).fetchone()
        if existing is not None:
            if existing[-1] != digest:
                raise LedgerConflictError(
                    "REVISION_IMMUTABLE",
                    f"revision {revision.reporting_revision_id} already exists with "
                    "different content; a restatement is a new revision",
                )
            return _revision_from_row(existing[:-1])
        obligation = await (
            await connection.execute(
                f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                " WHERE reporting_obligation_id = %s AND account_id = %s",
                (revision.reporting_obligation_id, revision.account_id),
            )
        ).fetchone()
        if obligation is None:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND",
                "a revision must attach to an obligation committed at the period close",
            )
        await self._require_writable_generation(
            connection, _obligation_from_row(obligation).generation_key
        )
        validate_revision_currency(_obligation_from_row(obligation), revision, rows)
        if revision.supersedes_reporting_revision_id:
            await self._require_current_leaf(connection, revision)
        write_error = None
        try:
            await connection.execute(
                "INSERT INTO reporting_revisions"
                " (reporting_revision_id, account_id, reporting_obligation_id, finality,"
                "  revision_content_sha256, row_count, control_totals, observed_at,"
                "  data_through, created_at, supersedes_reporting_revision_id,"
                "  finality_basis, finality_policy_id, finalized_at, readable,"
                "  readable_at_commit, source_publication_id, source_manifest_sha256,"
                "  content_sha256, canonical_content_digest, managed_control_totals)"
                " VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s, %s, %s, %s, %s,"
                "         %s, %s, %s, %s, %s, %s::jsonb, %s::jsonb)",
                (
                    revision.reporting_revision_id,
                    revision.account_id,
                    revision.reporting_obligation_id,
                    revision.finality,
                    revision.revision_content_sha256,
                    revision.row_count,
                    _json([[name, value] for name, value in revision.control_totals]),
                    revision.observed_at,
                    revision.data_through,
                    revision.created_at,
                    revision.supersedes_reporting_revision_id,
                    revision.finality_basis,
                    revision.finality_policy_id,
                    revision.finalized_at,
                    revision.readable,
                    revision.readable_at_commit,
                    revision.source_publication_id,
                    revision.source_manifest_sha256,
                    digest,
                    (
                        _json(revision.canonical_content_digest.to_wire())
                        if revision.canonical_content_digest is not None
                        else None
                    ),
                    (
                        _json([item.to_wire() for item in revision.managed_control_totals])
                        if revision.managed_control_totals is not None
                        else None
                    ),
                ),
            )
        except Exception as error:  # psycopg raises UniqueViolation subclasses
            write_error = _translate_integrity_error(error)
        if write_error is not None:
            raise write_error
        if rows:
            await connection.cursor().executemany(
                "INSERT INTO reporting_revision_rows"
                " (reporting_revision_id, ordinal, row_payload)"
                " SELECT reporting_revision_id, %s, %s::jsonb FROM reporting_revisions"
                " WHERE account_id = %s AND reporting_revision_id = %s",
                [
                    (ordinal, _json(row), revision.account_id, revision.reporting_revision_id)
                    for ordinal, row in enumerate(rows)
                ],
            )
        await self._append_change(
            connection,
            revision.account_id,
            "revision",
            revision.reporting_revision_id,
            consumer_id=_obligation_from_row(obligation).consumer_id,
        )
        if self._notifications_enabled:
            from adcp.reporting.ledger.notification_events import revision_event

            await self._record_notification(
                connection,
                revision_event(
                    revision,
                    await self._notification_now(connection),
                    consumer_id=_obligation_from_row(obligation).consumer_id,
                ),
            )
            await self._dirty_status(
                connection,
                ReportingStatusScope.for_obligation(_obligation_from_row(obligation)),
                "revision",
                after=ReportingStatusEvidence(
                    "revision",
                    revision.reporting_revision_id,
                    readable=revision.readable,
                    supersedes_id=revision.supersedes_reporting_revision_id,
                ),
            )
    return revision
async def create_schema(self) ‑> None
Expand source code
async def create_schema(self) -> None:
    """Create or upgrade the ledger atomically, serializing concurrent boots.

    The bootstrap takes a transaction-scoped schema lock before any DDL.
    Keep the migration in that transaction, including with an autocommit
    pool, so a second process cannot observe a partially upgraded schema.
    """
    async with self._connection() as connection:
        async with connection.transaction():
            await self._create_schema_on(connection)

Create or upgrade the ledger atomically, serializing concurrent boots.

The bootstrap takes a transaction-scoped schema lock before any DDL. Keep the migration in that transaction, including with an autocommit pool, so a second process cannot observe a partially upgraded schema.

async def ensure_issue_opened(self,
*,
issue_key: str,
account_id: str,
consumer_id: str | None,
observed_at: datetime,
status_scope: ReportingStatusScope | None = None) ‑> ReportingIssueLifecycle
Expand source code
async def ensure_issue_opened(
    self,
    *,
    issue_key: str,
    account_id: str,
    consumer_id: str | None,
    observed_at: datetime,
    status_scope: ReportingStatusScope | None = None,
) -> ReportingIssueLifecycle:
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, account_id)
        return await self._ensure_issue_opened_on(
            connection,
            issue_key=issue_key,
            account_id=account_id,
            consumer_id=consumer_id,
            observed_at=observed_at,
            status_scope=status_scope,
        )
async def find_obligation(self,
*,
account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
period_start: datetime,
period_end: datetime) ‑> ReportingObligationRecord | None
Expand source code
async def find_obligation(
    self,
    *,
    account_id: str,
    consumer_id: str,
    delivery_config_id: str,
    delivery_config_version: int,
    period_start: datetime,
    period_end: datetime,
) -> ReportingObligationRecord | None:
    key = ReportingConfigurationGenerationKey(
        account_id=account_id,
        consumer_id=consumer_id,
        delivery_config_id=delivery_config_id,
        delivery_config_version=delivery_config_version,
    )
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                " AND delivery_config_version = %s AND period_start = %s AND period_end = %s",
                (
                    key.account_id,
                    key.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                    period_start,
                    period_end,
                ),
            )
        ).fetchone()
    return _obligation_from_row(row) if row else None
async def get_issue(self, *, issue_key: str, account_id: str) ‑> ReportingIssueLifecycle | None
Expand source code
async def get_issue(self, *, issue_key: str, account_id: str) -> ReportingIssueLifecycle | None:
    async with self._connection() as connection:
        return await self._live_issue(connection, issue_key, account_id)
async def get_obligation(self, *, account_id: str, reporting_obligation_id: str) ‑> ReportingObligationRecord | None
Expand source code
async def get_obligation(
    self, *, account_id: str, reporting_obligation_id: str
) -> ReportingObligationRecord | None:
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND reporting_obligation_id = %s",
                (account_id, reporting_obligation_id),
            )
        ).fetchone()
    return _obligation_from_row(row) if row else None
async def get_provisional_acquisition(self, *, account_id: str, reporting_obligation_id: str, ordinal: int) ‑> ProvisionalAcquisition | None
Expand source code
async def get_provisional_acquisition(
    self, *, account_id: str, reporting_obligation_id: str, ordinal: int
) -> ProvisionalAcquisition | None:
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                "SELECT payload FROM reporting_provisional_acquisitions"
                " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                (account_id, reporting_obligation_id, ordinal),
            )
        ).fetchone()
    return ProvisionalAcquisition.from_wire(row[0]) if row is not None else None
async def get_provisional_observation(self, *, account_id: str, reporting_obligation_id: str) ‑> ProvisionalObservation | None
Expand source code
async def get_provisional_observation(
    self, *, account_id: str, reporting_obligation_id: str
) -> ProvisionalObservation | None:
    async with self._connection() as connection:
        await self._require_provisional_schema(connection)
        row = await (
            await connection.execute(
                "SELECT payload FROM reporting_provisional_observations"
                " WHERE account_id=%s AND reporting_obligation_id=%s"
                " ORDER BY ordinal DESC LIMIT 1",
                (account_id, reporting_obligation_id),
            )
        ).fetchone()
    return ProvisionalObservation.from_wire(row[0]) if row else None
async def get_restatement_checkpoint(self, *, account_id: str, reporting_obligation_id: str) ‑> RestatementCheckpoint | None
Expand source code
async def get_restatement_checkpoint(
    self, *, account_id: str, reporting_obligation_id: str
) -> RestatementCheckpoint | None:
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                "SELECT account_id, reporting_obligation_id, checked_at,"
                " next_observation, provisional_until"
                " FROM reporting_restatement_checkpoints"
                " WHERE account_id = %s AND reporting_obligation_id = %s",
                (account_id, reporting_obligation_id),
            )
        ).fetchone()
    if row is None:
        return None
    return RestatementCheckpoint(
        account_id=row[0],
        reporting_obligation_id=row[1],
        checked_at=_utc(row[2]),
        next_observation=int(row[3]),
        provisional_until=_utc(row[4]) if row[4] else None,
    )
async def get_retry_schedule(self, *, scope_key: str) ‑> RetryScheduleEntry | None
Expand source code
async def get_retry_schedule(self, *, scope_key: str) -> RetryScheduleEntry | None:
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                "SELECT retry_not_before, attempt, blocked, recorded_at"
                " FROM adcp_reporting_producer_retry_schedules"
                " WHERE scope_key = %s",
                (scope_key,),
            )
        ).fetchone()
    return (
        RetryScheduleEntry(scope_key, _utc(row[0]), int(row[1]), row[2], _utc(row[3]))
        if row
        else None
    )
async def get_revision(self, *, account_id: str, reporting_revision_id: str) ‑> ReportingRevisionRecord | None
Expand source code
async def get_revision(
    self, *, account_id: str, reporting_revision_id: str
) -> ReportingRevisionRecord | None:
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                f"SELECT {_REVISION_COLUMNS} FROM reporting_revisions"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND reporting_revision_id = %s",
                (account_id, reporting_revision_id),
            )
        ).fetchone()
    return _revision_from_row(row) if row else None
async def lease_period_close(self, *, worker_id: str, now: datetime, lease_seconds: float) ‑> LeasedConfiguration | None
Expand source code
async def lease_period_close(
    self, *, worker_id: str, now: datetime, lease_seconds: float
) -> LeasedConfiguration | None:
    from psycopg.errors import LockNotAvailable
    from psycopg.pq import TransactionStatus

    moment = _utc(now)
    expires = moment + timedelta(seconds=lease_seconds)
    after = self._period_close_sample
    wait_until = None
    following = None
    result = None
    async with self._connection() as connection:
        # Never add a blocking account edge while a caller transaction may
        # already own locks. Standalone turns may queue only before the first
        # account acquisition, including acquisitions that find a stale row.
        can_wait = connection.info.transaction_status == TransactionStatus.IDLE
        async with connection.transaction():
            await self._bound_lock_waits(connection)
            # A materializer installed by any participant adds an AFTER UPDATE
            # trigger taking the account lock. Sample without row locks, then take
            # that account lock before locking a configuration. The base store can
            # share that schema, so the ordering belongs here, not only on a tier.
            for _ in range(2):
                continuation = (
                    " AND (COALESCE(t.lease_turn,0),"
                    " COALESCE(c.lease_expires_at,'-infinity'::timestamptz),"
                    " c.account_id,c.consumer_id,c.delivery_config_id,c.delivery_confi"
                    "g_version)"
                    " > (%s,COALESCE(%s::timestamptz,'-infinity'::timestamptz),%s,%s,%s,%s)"
                    if after is not None
                    else ""
                )
                query = (
                    "SELECT c.account_id,c.consumer_id,c.delivery_config_id,c.delivery"
                    "_config_version,"  # nosec B608
                    " COALESCE(t.lease_turn,0),c.lease_expires_at FROM"
                    " reporting_configurations c"
                    " LEFT JOIN adcp_reporting_configuration_lease_turns t"
                    " ON (t.account_id,t.consumer_id,t.delivery_config_id,t.delivery_c"
                    "onfig_version)"
                    " = (c.account_id,c.consumer_id,c.delivery_config_id,c.delivery_co"
                    "nfig_version)"
                    " WHERE NOT c.quarantined AND (c.lease_expires_at IS NULL OR c.lea"
                    "se_expires_at<=%s)"
                    + continuation
                    + " ORDER BY COALESCE(t.lease_turn,0),c.lease_expires_at NULLS FIRST,"
                    " c.account_id,c.consumer_id,c.delivery_config_id,c.delivery_confi"
                    "g_version LIMIT 32"
                )
                rows = await (
                    await connection.execute(query, (moment, *(after or ())))
                ).fetchall()
                if rows or after is None:
                    break
                after = None
            for candidate in rows:
                row = candidate[:4]
                following = (candidate[4], candidate[5], *row)
                locked = await (
                    await connection.execute(
                        "SELECT pg_try_advisory_xact_lock(hashtext('adcp.reporting:' || %s))",
                        (row[0],),
                    )
                ).fetchone()
                if not locked[0]:
                    if not can_wait:
                        continue
                    # A try-lock alone can starve behind a continuous queue of
                    # ordinary writers, even though each writer commits promptly.
                    # Join that queue briefly, sharing one wait budget per turn.
                    # The savepoint rolls back a timed-out wait and its SET LOCAL;
                    # successful acquisition restores the caller's lock timeout.
                    if wait_until is None:
                        wait_until = monotonic() + _LEASE_ACCOUNT_WAIT_SECONDS
                    remaining_ms = int((wait_until - monotonic()) * 1000)
                    if remaining_ms <= 0:
                        continue
                    try:
                        async with connection.transaction():
                            previous, configured_ms = await (
                                await connection.execute(
                                    "SELECT current_setting('lock_timeout'),setting::integer"
                                    " FROM pg_settings WHERE name='lock_timeout'"
                                )
                            ).fetchone()
                            limit_ms = min(remaining_ms, configured_ms or remaining_ms)
                            await connection.execute(
                                "SELECT set_config('lock_timeout',%s,true)", (f"{limit_ms}ms",)
                            )
                            await connection.execute(
                                "SELECT pg_advisory_xact_lock(hashtext('adcp.reporting:' ||"
                                " %s))",
                                (row[0],),
                            )
                            await connection.execute(
                                "SELECT set_config('lock_timeout',%s,true)", (previous,)
                            )
                    except LockNotAvailable:
                        continue
                can_wait = False
                snapshot = await self._period_close_generations_on(connection, tuple(row))
                acquired = await (
                    await connection.execute(
                        "UPDATE reporting_configurations SET lease_worker_id = %s,"
                        " lease_expires_at = %s"
                        " WHERE (account_id,consumer_id,delivery_config_id,delivery_config"
                        "_version) = ("
                        " SELECT c.account_id,c.consumer_id,c.delivery_config_id,c.deliver"
                        "y_config_version"
                        " FROM reporting_configurations c WHERE c.account_id=%s"
                        " AND c.consumer_id=%s AND c.delivery_config_id=%s AND c.delivery_"
                        "config_version=%s"
                        " AND NOT c.quarantined AND (c.lease_expires_at IS NULL OR c.lease"
                        "_expires_at<=%s)"
                        " FOR UPDATE OF c SKIP LOCKED) RETURNING account_id",
                        (worker_id, expires, *row, moment),
                    )
                ).fetchone()
                if acquired is None:
                    continue
                await self._restore_period_close_generations_on(
                    connection, tuple(row), snapshot
                )
                await connection.execute(
                    "INSERT INTO adcp_reporting_configuration_lease_turns"
                    " (account_id,consumer_id,delivery_config_id,delivery_config_versi"
                    "on,lease_turn)"
                    " VALUES(%s,%s,%s,%s,nextval('adcp_reporting_configuration_lease_t"
                    "urn_seq'))"
                    " ON CONFLICT(account_id,consumer_id,delivery_config_id,delivery_c"
                    "onfig_version)"
                    " DO UPDATE SET"
                    " lease_turn=nextval('adcp_reporting_configuration_lease_turn_seq')",
                    tuple(row),
                )
                result = LeasedConfiguration(row[0], row[1], row[2], row[3], expires)
                break
    # Only sampling uses this hint: leases and fairness ranks stay transactional.
    # Continue past a busy prefix on the next turn; a successful turn returns to
    # durable fairness order. Empty tails wrap at most once without row locks.
    self._period_close_sample = following if result is None else None
    return result
async def list_adjustments(self, *, account_id: str, reporting_revision_ids: Sequence[str]) ‑> tuple[ReportingAdjustmentRecord, ...]
Expand source code
async def list_adjustments(
    self, *, account_id: str, reporting_revision_ids: Sequence[str]
) -> tuple[ReportingAdjustmentRecord, ...]:
    if not reporting_revision_ids:
        return ()
    async with self._connection() as connection:
        rows = await (
            await connection.execute(
                f"SELECT {_ADJUSTMENT_COLUMNS} FROM reporting_adjustments"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND adjusts_reporting_revision_id = ANY(%s)"
                " ORDER BY created_at, reporting_adjustment_id",
                (account_id, list(reporting_revision_ids)),
            )
        ).fetchall()
    return tuple(_adjustment_from_row(row) for row in rows)
async def list_all_configurations(self) ‑> tuple[ReportingConfiguration, ...]
Expand source code
async def list_all_configurations(self) -> tuple[ReportingConfiguration, ...]:
    """One ordered scan for trusted service recovery, including retired generations."""
    async with self._connection() as connection:
        rows = await (
            await connection.execute(
                "SELECT delivery_config_id, delivery_config_version, account_id,"
                " report_definition_id, reporting_profile, feed_purpose, required_finality,"
                " account_timezone, schedule, media_buy_ids, activated_at, deactivated_at,"
                " automated_recovery_seconds, status_retention_days, definition,"
                " authoritative_party, consumer_id, quarantined"
                " FROM reporting_configurations WHERE NOT quarantined"
                " ORDER BY account_id, consumer_id, delivery_config_id, delivery_config_version"
            )
        ).fetchall()
    return tuple(_configuration_from_row(row) for row in rows)

One ordered scan for trusted service recovery, including retired generations.

async def list_configurations(self,
*,
caller: ReportingCaller,
delivery_config_ids: Sequence[str] | None = None) ‑> tuple[ReportingConfiguration, ...]
Expand source code
async def list_configurations(
    self, *, caller: ReportingCaller, delivery_config_ids: Sequence[str] | None = None
) -> tuple[ReportingConfiguration, ...]:
    async with self._connection() as connection:
        return await self._list_configurations_on(
            connection,
            account_id=caller.account_id,
            consumer_id=caller.consumer_id,
            delivery_config_ids=delivery_config_ids,
        )
async def list_consumer_statuses(self,
*,
account_id: str,
consumer_id: str,
reporting_obligation_ids: Sequence[str] | None = None) ‑> tuple[ConsumerStatusRecord, ...]
Expand source code
async def list_consumer_statuses(
    self,
    *,
    account_id: str,
    consumer_id: str,
    reporting_obligation_ids: Sequence[str] | None = None,
) -> tuple[ConsumerStatusRecord, ...]:
    # Attaches by obligation id *or* by the exact logical period key, so a
    # chain filed before the obligation existed is not lost, forked, or
    # reset when the seller later repairs the missing obligation.
    clause = ""
    params: list[Any] = [account_id, consumer_id]
    if reporting_obligation_ids is not None:
        clause = (
            " AND EXISTS ("
            "   SELECT 1 FROM reporting_obligations o"
            "   WHERE o.reporting_obligation_id = ANY(%s)"
            "     AND o.account_id = s.account_id"
            "     AND (o.reporting_obligation_id = s.reporting_obligation_id OR ("
            "         o.delivery_config_id = s.delivery_config_id"
            "     AND o.delivery_config_version = s.delivery_config_version"
            "     AND o.report_definition_id = s.report_definition_id"
            "     AND o.period_start = s.period_start"
            "     AND o.period_end = s.period_end)))"
        )
        params.append(list(reporting_obligation_ids))
    async with self._connection() as connection:
        rows = await (
            await connection.execute(
                f"SELECT {_STATUS_COLUMNS} FROM reporting_consumer_statuses s"  # noqa: S608  # nosec B608
                f" WHERE s.account_id = %s AND s.consumer_id = %s{clause}"
                " ORDER BY s.recorded_at, s.reporting_status_id",
                tuple(params),
            )
        ).fetchall()
    return tuple(_status_from_row(row) for row in rows)
async def list_revisions(self, *, account_id: str, reporting_obligation_id: str) ‑> tuple[ReportingRevisionRecord, ...]
Expand source code
async def list_revisions(
    self, *, account_id: str, reporting_obligation_id: str
) -> tuple[ReportingRevisionRecord, ...]:
    async with self._connection() as connection:
        rows = await (
            await connection.execute(
                f"SELECT {_REVISION_COLUMNS} FROM reporting_revisions"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND reporting_obligation_id = %s"
                " ORDER BY created_at, reporting_revision_id",
                (account_id, reporting_obligation_id),
            )
        ).fetchall()
    return tuple(_revision_from_row(row) for row in rows)
async def open_snapshot(self,
*,
caller: ReportingCaller,
filters_fingerprint: str) ‑> LedgerSnapshot
Expand source code
async def open_snapshot(
    self, *, caller: ReportingCaller, filters_fingerprint: str
) -> LedgerSnapshot:
    account_id = caller.account_id
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                "SELECT COALESCE(MAX(seq), 0), now() FROM reporting_ledger_changes"
                " WHERE account_id=%s AND consumer_id=%s AND record_kind IN"
                " ('obligation','revision','adjustment','consumer_status')",
                (account_id, caller.consumer_id),
            )
        ).fetchone()
    assert row is not None
    max_sequence = int(row[0])
    as_of = _utc(self._clock()) if self._clock is not None else _utc(row[1])
    return LedgerSnapshot(
        snapshot_id="rpls_"
        + _fingerprint([account_id, caller.consumer_id, filters_fingerprint, max_sequence])[
            :32
        ],
        account_id=account_id,
        consumer_id=caller.consumer_id,
        ledger_as_of=as_of,
        max_sequence=max_sequence,
    )
async def put_configuration(self,
configuration: ReportingConfiguration) ‑> None
Expand source code
async def put_configuration(self, configuration: ReportingConfiguration) -> None:
    reject_reserved_authoritative_party(configuration)
    key = configuration.generation_key
    payload = _configuration_payload(configuration)
    digest = _fingerprint(payload)
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, key.account_id)
        inserted_cursor = await connection.execute(
            "INSERT INTO reporting_configurations"
            " (delivery_config_id, delivery_config_version, account_id,"
            "  report_definition_id, reporting_profile, feed_purpose, required_finality,"
            "  account_timezone, schedule, media_buy_ids, activated_at, deactivated_at,"
            "  automated_recovery_seconds, status_retention_days, definition,"
            "  authoritative_party, content_sha256, consumer_id, quarantined)"
            " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s::jsonb, %s, %s, %s, %s,"
            "         %s::jsonb, %s, %s, %s, %s)"
            " ON CONFLICT (account_id, consumer_id, delivery_config_id, delive"
            "ry_config_version) DO NOTHING"
            " RETURNING delivery_config_id",
            (
                key.delivery_config_id,
                key.delivery_config_version,
                key.account_id,
                configuration.report_definition_id,
                configuration.reporting_profile,
                configuration.feed_purpose,
                configuration.required_finality,
                configuration.account_timezone,
                _json(payload["schedule"]),
                _json(sorted(configuration.media_buy_ids)),
                configuration.activated_at,
                configuration.deactivated_at,
                configuration.automated_recovery_window.total_seconds(),
                configuration.status_retention_days,
                _json(payload["definition"]) if payload["definition"] else None,
                configuration.authoritative_party,
                digest,
                configuration.consumer_id,
                configuration.quarantined,
            ),
        )
        inserted = await inserted_cursor.fetchone()
        # Check *after* the insert. ON CONFLICT waits for a concurrent
        # winner; a fresh READ COMMITTED statement sees its retained
        # content. A pre-insert check followed by DO NOTHING could silently
        # accept a different immutable generation from a losing writer.
        row = await (
            await connection.execute(
                "SELECT content_sha256, activated_at, deactivated_at,"
                " automated_recovery_seconds, status_retention_days, quarantined"
                " FROM reporting_configurations"
                " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                " AND delivery_config_version = %s FOR UPDATE",
                (
                    key.account_id,
                    key.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                ),
            )
        ).fetchone()
        if row is not None and row[5]:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED", "legacy evidence is read-only"
            )
        if row is None or row[0] != digest:
            raise LedgerConflictError(
                "CONFIGURATION_GENERATION_IMMUTABLE",
                f"configuration {key.delivery_config_id}@{key.delivery_config_version} "
                "already exists with different content for this account; publish a new "
                "version instead of editing a retained generation",
            )

        scope = ReportingStatusScope(configuration.account_id, configuration.generation_key)
        if inserted is not None:
            if self._notifications_enabled:
                await self._dirty_status(
                    connection,
                    scope,
                    "configuration",
                    after=configuration_evidence(configuration),
                )
            return
        # rc.3 carries activation/deactivation and the recovery/retention
        # windows as lifecycle state over one immutable generation, so a
        # re-put that changes only those must apply -- otherwise a
        # deactivated feed keeps minting obligations -- and must co-commit
        # its status-dirty generation on this exact connection. An
        # unchanged re-put stays a no-op and enqueues nothing.
        retained = replace(
            configuration,
            activated_at=_utc(row[1]) if row[1] else None,
            deactivated_at=_utc(row[2]) if row[2] else None,
            automated_recovery_window=timedelta(seconds=float(row[3])),
            status_retention_days=row[4],
        )
        if configuration_lifecycle(retained) == configuration_lifecycle(configuration):
            return
        await connection.execute(
            "UPDATE reporting_configurations SET activated_at = %s, deactivated_at = %s,"
            " automated_recovery_seconds = %s, status_retention_days = %s"
            " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
            " AND delivery_config_version = %s",
            (
                configuration.activated_at,
                configuration.deactivated_at,
                configuration.automated_recovery_window.total_seconds(),
                configuration.status_retention_days,
                key.account_id,
                key.consumer_id,
                key.delivery_config_id,
                key.delivery_config_version,
            ),
        )
        if self._notifications_enabled:
            await self._dirty_status(
                connection,
                scope,
                "configuration",
                before=configuration_evidence(retained),
                after=configuration_evidence(configuration),
            )
async def read_page(self,
*,
snapshot: LedgerSnapshot,
consumer_id: str | None,
delivery_config_ids: Sequence[str] | None,
media_buy_ids: Sequence[str] | None,
offset: int,
limit: int,
changes_after_sequence: int | None,
feed_purposes: Sequence[str] | None = None,
period_start: datetime | None = None,
period_end: datetime | None = None) ‑> LedgerPage
Expand source code
async def read_page(
    self,
    *,
    snapshot: LedgerSnapshot,
    consumer_id: str | None,
    delivery_config_ids: Sequence[str] | None,
    media_buy_ids: Sequence[str] | None,
    offset: int,
    limit: int,
    changes_after_sequence: int | None,
    feed_purposes: Sequence[str] | None = None,
    period_start: datetime | None = None,
    period_end: datetime | None = None,
) -> LedgerPage:
    if consumer_id not in {None, snapshot.consumer_id}:
        raise LedgerConflictError(
            "CURSOR_SNAPSHOT_MISMATCH", "snapshot belongs to another caller"
        )
    consumer_id = snapshot.consumer_id
    lower = changes_after_sequence or 0
    async with self._connection() as connection:
        rows = await (
            await connection.execute(
                "SELECT seq, record_kind, record_id FROM reporting_ledger_changes"
                " WHERE account_id = %s AND consumer_id=%s AND seq > %s AND seq <= %s"
                " ORDER BY seq",
                (snapshot.account_id, consumer_id, lower, snapshot.max_sequence),
            )
        ).fetchall()

        selected: list[tuple[int, LedgerRecordKind, Any]] = []
        for _seq, kind, record_id in rows:
            record = await self._resolve(
                connection, snapshot.account_id, kind, record_id, consumer_id=consumer_id
            )
            if record is None:
                continue
            if not await self._in_scope(
                connection,
                kind,
                record,
                delivery_config_ids,
                media_buy_ids,
                consumer_id,
                feed_purposes,
                period_start,
                period_end,
            ):
                continue
            selected.append((_seq, kind, record))

    window = selected[offset : offset + limit]
    has_more = offset + limit < len(selected)
    return LedgerPage(
        obligations=tuple(item[2] for item in window if item[1] == "obligation"),
        revisions=tuple(item[2] for item in window if item[1] == "revision"),
        adjustments=tuple(item[2] for item in window if item[1] == "adjustment"),
        consumer_statuses=tuple(item[2] for item in window if item[1] == "consumer_status"),
        total_count=len(selected),
        has_more=has_more,
        cursor=(
            encode_cursor({"snapshot": snapshot.snapshot_id, "offset": offset + limit})
            if has_more
            else None
        ),
    )
async def read_revision_rows(self,
*,
account_id: str,
reporting_revision_id: str,
cursor: str | None = None,
limit: int = 500) ‑> ReportingRowPage
Expand source code
async def read_revision_rows(
    self,
    *,
    account_id: str,
    reporting_revision_id: str,
    cursor: str | None = None,
    limit: int = 500,
) -> ReportingRowPage:
    from adcp.reporting.ledger.store import revision_row_offset

    offset = revision_row_offset(cursor, reporting_revision_id, limit)
    async with self._connection() as connection:
        owned = await (
            await connection.execute(
                "SELECT row_count FROM reporting_revisions"
                " WHERE account_id = %s AND reporting_revision_id = %s",
                (account_id, reporting_revision_id),
            )
        ).fetchone()
        if owned is None:
            raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
        rows = await (
            await connection.execute(
                "SELECT row_payload FROM reporting_revision_rows"
                " WHERE reporting_revision_id = %s"
                " ORDER BY ordinal OFFSET %s LIMIT %s",
                (reporting_revision_id, offset, limit),
            )
        ).fetchall()
    total = int(owned[0])
    has_more = offset + limit < total
    return ReportingRowPage(
        reporting_revision_id=reporting_revision_id,
        rows=tuple(row[0] for row in rows),
        total_count=total,
        has_more=has_more,
        cursor=(
            encode_cursor(
                {"ownership": 2, "revision": reporting_revision_id, "offset": offset + limit}
            )
            if has_more
            else None
        ),
    )
async def read_status_snapshot(self,
*,
caller: ReportingCaller) ‑> ReportingStatusSnapshot
Expand source code
async def read_status_snapshot(self, *, caller: ReportingCaller) -> ReportingStatusSnapshot:
    from adcp.reporting.ledger.delivery_models import ReportingDeliveryPrincipal
    from adcp.reporting.ledger.status_snapshot import settle_snapshot_on
    from adcp.reporting.materializer.capture import private_snapshot

    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, caller.account_id)
        return private_snapshot(
            await settle_snapshot_on(self, connection, account_id=caller.account_id),
            ReportingDeliveryPrincipal(caller.account_id, caller.consumer_id),
        )
async def record_consumer_status(self,
status: ConsumerStatusRecord) ‑> tuple[ConsumerStatusRecord, bool]
Expand source code
async def record_consumer_status(
    self, status: ConsumerStatusRecord
) -> tuple[ConsumerStatusRecord, bool]:
    from adcp.reporting.evidence import consumer_reference

    consumer_reference(status.consumer_id)
    key = status.generation_key
    digest = _fingerprint(_consumer_status_payload(status))
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, status.account_id)
        replay = await self._replay(connection, status, digest)
        if replay is not None:
            return replay, False

        from adcp.reporting.ledger.status_snapshot import (
            read_snapshot_on,
            validate_status_evidence,
        )

        validate_status_evidence(
            status,
            await read_snapshot_on(
                connection,
                account_id=status.account_id,
                clock=self._clock,
                include_issue_scopes=await self._issue_scope_storage_on(connection),
            ),
        )

        leaf = await (
            await connection.execute(
                "SELECT reporting_status_id FROM reporting_consumer_statuses"
                " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                "   AND delivery_config_version = %s AND report_definition_id = %s"
                "   AND period_start = %s AND period_end = %s AND superseded = FALSE",
                (
                    key.account_id,
                    status.consumer_id,
                    key.delivery_config_id,
                    key.delivery_config_version,
                    status.report_definition_id,
                    status.period_start,
                    status.period_end,
                ),
            )
        ).fetchone()
        if status.supersedes_reporting_status_id:
            if leaf is None or leaf[0] != status.supersedes_reporting_status_id:
                raise LedgerConflictError(
                    "STATUS_SUPERSEDES_STALE",
                    "supersedes_reporting_status_id must name this chain's current leaf; "
                    "a stale pointer would let a successful retry erase a recorded outage",
                )
            await connection.execute(
                "UPDATE reporting_consumer_statuses SET superseded = TRUE"
                " WHERE account_id = %s AND consumer_id = %s AND reporting_status_id = %s",
                (key.account_id, status.consumer_id, status.supersedes_reporting_status_id),
            )
        elif leaf is not None:
            raise LedgerConflictError(
                "STATUS_SUPERSEDES_REQUIRED",
                "this chain already has a current statement; a new statement must "
                "explicitly supersede it",
            )
        try:
            await connection.execute(
                "INSERT INTO reporting_consumer_statuses"
                " (reporting_status_id, account_id, consumer_id, delivery_config_id,"
                "  delivery_config_version, report_definition_id, period_start, period_end,"
                "  period_source_timezone, consumer_status, status_as_of, recorded_at,"
                "  supersedes_reporting_status_id, reporting_obligation_id,"
                "  reporting_revision_id, observed_revision_content_sha256, failure_code,"
                "  mismatch_code, consumer_commit_ref, seller_ledger_snapshot_id,"
                "  seller_ledger_as_of, superseded, content_sha256)"
                " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,"
                "         %s, %s, %s, %s, %s, FALSE, %s)",
                (
                    status.reporting_status_id,
                    status.account_id,
                    status.consumer_id,
                    status.delivery_config_id,
                    status.delivery_config_version,
                    status.report_definition_id,
                    status.period_start,
                    status.period_end,
                    status.period_source_timezone,
                    status.consumer_status,
                    status.status_as_of,
                    status.recorded_at,
                    status.supersedes_reporting_status_id,
                    status.reporting_obligation_id,
                    status.reporting_revision_id,
                    status.observed_revision_content_sha256,
                    status.failure_code,
                    status.mismatch_code,
                    status.consumer_commit_ref,
                    status.seller_ledger_snapshot_id,
                    status.seller_ledger_as_of,
                    digest,
                ),
            )
        except Exception as error:
            raise _translate_integrity_error(error) from error
        await self._append_change(
            connection,
            status.account_id,
            "consumer_status",
            status.reporting_status_id,
            consumer_id=status.consumer_id,
        )
        from adcp.reporting.ledger.status_snapshot import settle_snapshot_on

        await settle_snapshot_on(self, connection, account_id=status.account_id)
        if self._notifications_enabled:
            await self._dirty_status(
                connection,
                ReportingStatusScope(
                    status.account_id,
                    status.generation_key,
                    status.reporting_obligation_id,
                    status.consumer_id,
                ),
                "consumer_status",
                ReportingStatusEvidence("consumer_status", leaf[0]) if leaf else None,
                ReportingStatusEvidence(
                    "consumer_status",
                    status.reporting_status_id,
                    supersedes_id=status.supersedes_reporting_status_id,
                ),
            )
    return status, True
async def record_consumer_status_with_lifecycle(self,
status: ConsumerStatusRecord) ‑> tuple[ConsumerStatusRecord, bool]
Expand source code
async def record_consumer_status_with_lifecycle(
    self, status: ConsumerStatusRecord
) -> tuple[ConsumerStatusRecord, bool]:
    """Optional participant: the record and lifecycle share the source transaction."""
    return await self.record_consumer_status(status)

Optional participant: the record and lifecycle share the source transaction.

async def record_restatement_checkpoint(self,
checkpoint: RestatementCheckpoint) ‑> RestatementCheckpoint
Expand source code
async def record_restatement_checkpoint(
    self, checkpoint: RestatementCheckpoint
) -> RestatementCheckpoint:
    async with self._connection() as connection:
        obligation = await (
            await connection.execute(
                "SELECT 1 FROM reporting_obligations"
                " WHERE reporting_obligation_id = %s AND account_id = %s",
                (checkpoint.reporting_obligation_id, checkpoint.account_id),
            )
        ).fetchone()
        if obligation is None:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND",
                "a restatement checkpoint must attach to an obligation for this account",
            )
        row = await (
            await connection.execute(
                "INSERT INTO reporting_restatement_checkpoints"
                " (account_id, reporting_obligation_id, checked_at, next_observation,"
                "  provisional_until)"
                " VALUES (%s, %s, %s, %s, %s)"
                " ON CONFLICT (reporting_obligation_id) DO UPDATE SET"
                " checked_at = EXCLUDED.checked_at,"
                " next_observation = EXCLUDED.next_observation,"
                " provisional_until = EXCLUDED.provisional_until"
                " WHERE reporting_restatement_checkpoints.account_id = EXCLUDED.account_id"
                "   AND (reporting_restatement_checkpoints.next_observation"
                "        < EXCLUDED.next_observation"
                "     OR (reporting_restatement_checkpoints.next_observation"
                "         = EXCLUDED.next_observation"
                "         AND reporting_restatement_checkpoints.checked_at"
                "             < EXCLUDED.checked_at))"
                " RETURNING account_id, reporting_obligation_id, checked_at,"
                " next_observation, provisional_until",
                (
                    checkpoint.account_id,
                    checkpoint.reporting_obligation_id,
                    checkpoint.checked_at,
                    checkpoint.next_observation,
                    checkpoint.provisional_until,
                ),
            )
        ).fetchone()
    if row is None:
        stored = await self.get_restatement_checkpoint(
            account_id=checkpoint.account_id,
            reporting_obligation_id=checkpoint.reporting_obligation_id,
        )
        if stored is None:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND",
                "a restatement checkpoint must attach to an obligation for this account",
            )
        return stored
    return RestatementCheckpoint(
        account_id=row[0],
        reporting_obligation_id=row[1],
        checked_at=_utc(row[2]),
        next_observation=int(row[3]),
        provisional_until=_utc(row[4]) if row[4] else None,
    )
async def record_retry_schedule(self,
entry: RetryScheduleEntry) ‑> RetryScheduleEntry
Expand source code
async def record_retry_schedule(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
    async with self._connection() as connection:
        row = await (
            await connection.execute(
                "INSERT INTO adcp_reporting_producer_retry_schedules"
                " (scope_key, retry_not_before, attempt, blocked, recorded_at)"
                " VALUES (%s, %s, %s, %s, %s)"
                " ON CONFLICT (scope_key) DO UPDATE SET"
                " retry_not_before = CASE WHEN EXCLUDED.attempt = 0 OR EXCLUDED.blocked"
                " OR adcp_reporting_producer_retry_schedules.blocked OR %s"
                " THEN EXCLUDED.retry_not_before ELSE GREATEST("
                " adcp_reporting_producer_retry_schedules.retry_not_before,"
                " EXCLUDED.retry_not_before) END,"
                " attempt = EXCLUDED.attempt, blocked = EXCLUDED.blocked,"
                " recorded_at = EXCLUDED.recorded_at"
                " WHERE EXCLUDED.recorded_at >="
                " adcp_reporting_producer_retry_schedules.recorded_at"
                " AND (EXCLUDED.attempt = 0 OR EXCLUDED.blocked OR %s"
                " OR (NOT adcp_reporting_producer_retry_schedules.blocked AND"
                " adcp_reporting_producer_retry_schedules.attempt <= EXCLUDED.attempt))"
                " RETURNING retry_not_before, attempt, blocked, recorded_at",
                (
                    entry.scope_key,
                    entry.retry_not_before,
                    entry.attempt,
                    entry.blocked,
                    entry.recorded_at,
                    entry.replayed,
                    entry.replayed,
                ),
            )
        ).fetchone()
    if row is None:
        stored = await self.get_retry_schedule(scope_key=entry.scope_key)
        assert stored is not None
        return stored
    return RetryScheduleEntry(entry.scope_key, _utc(row[0]), int(row[1]), row[2], _utc(row[3]))
async def release_period_close(self,
lease: LeasedConfiguration,
*,
worker_id: str) ‑> None
Expand source code
async def release_period_close(self, lease: LeasedConfiguration, *, worker_id: str) -> None:
    key = lease.generation_key
    identity = (
        key.account_id,
        key.consumer_id,
        key.delivery_config_id,
        key.delivery_config_version,
    )
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, key.account_id)
        snapshot = await self._period_close_generations_on(connection, identity)
        released = await (
            await connection.execute(
                "UPDATE reporting_configurations SET lease_worker_id = NULL,"
                " lease_expires_at = NULL"
                " WHERE account_id = %s AND consumer_id = %s AND delivery_config_id = %s"
                " AND delivery_config_version = %s AND lease_worker_id = %s"
                " AND lease_expires_at = %s RETURNING account_id",
                (*identity, worker_id, lease.lease_expires_at),
            )
        ).fetchone()
        if released is not None:
            await self._restore_period_close_generations_on(connection, identity, snapshot)
async def reserve_provisional_acquisition(self, acquisition: ProvisionalAcquisition) ‑> ProvisionalAcquisition
Expand source code
async def reserve_provisional_acquisition(
    self, acquisition: ProvisionalAcquisition
) -> ProvisionalAcquisition:
    async with self.transaction(), self._connection() as connection:
        await self._lock_account(connection, acquisition.account_id)
        await self._require_provisional_schema(connection)
        key = (acquisition.account_id, acquisition.obligation_id, acquisition.ordinal)
        existing = await (
            await connection.execute(
                "SELECT payload FROM reporting_provisional_acquisitions"
                " WHERE account_id=%s AND reporting_obligation_id=%s AND ordinal=%s",
                key,
            )
        ).fetchone()
        if existing is not None:
            return ProvisionalAcquisition.from_wire(existing[0])
        obligation = await self.get_obligation(
            account_id=acquisition.account_id,
            reporting_obligation_id=acquisition.obligation_id,
        )
        if obligation is None:
            raise LedgerConflictError("OBLIGATION_NOT_FOUND", "unknown observation obligation")
        if not acquisition.binds(obligation):
            raise LedgerConflictError("OBSERVATION_CONFLICT", "acquisition generation differs")
        duplicate = await (
            await connection.execute(
                "SELECT 1 FROM reporting_provisional_acquisitions"
                " WHERE account_id=%s AND source_execution_key=%s",
                (acquisition.account_id, acquisition.execution_key),
            )
        ).fetchone()
        if duplicate is not None:
            raise LedgerConflictError(
                "OBSERVATION_CONFLICT", "execution key is already reserved"
            )
        checkpoint = await self.get_restatement_checkpoint(
            account_id=acquisition.account_id,
            reporting_obligation_id=acquisition.obligation_id,
        )
        expected = (
            checkpoint.next_observation
            if checkpoint
            else len(
                await self.list_revisions(
                    account_id=acquisition.account_id,
                    reporting_obligation_id=acquisition.obligation_id,
                )
            )
        )
        if acquisition.ordinal != expected:
            raise LedgerConflictError("OBSERVATION_CONFLICT", "observation ordinal changed")
        await connection.execute(
            "INSERT INTO reporting_provisional_acquisitions"
            " (account_id,reporting_obligation_id,ordinal,source_execution_key,payload)"
            " VALUES (%s,%s,%s,%s,%s::jsonb)",
            (*key, acquisition.execution_key, _json(acquisition.to_wire())),
        )
        return ProvisionalAcquisition.from_wire(acquisition.to_wire())
async def resolve_consumer_status_replay(self,
status: ConsumerStatusRecord) ‑> ConsumerStatusRecord | None
Expand source code
async def resolve_consumer_status_replay(
    self, status: ConsumerStatusRecord
) -> ConsumerStatusRecord | None:
    async with self._connection() as connection:
        return await self._replay(
            connection, status, _fingerprint(_consumer_status_payload(status))
        )
async def retire_issue(self,
*,
issue_key: str,
account_id: str,
at: datetime,
status_scope: ReportingStatusScope | None = None) ‑> ReportingIssueLifecycle | None
Expand source code
async def retire_issue(
    self,
    *,
    issue_key: str,
    account_id: str,
    at: datetime,
    status_scope: ReportingStatusScope | None = None,
) -> ReportingIssueLifecycle | None:
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, account_id)
        return await self._retire_issue_on(
            connection,
            issue_key=issue_key,
            account_id=account_id,
            at=at,
            status_scope=status_scope,
        )
async def set_issue_state(self,
*,
issue_key: str,
account_id: str,
state: "Literal['acknowledged', 'waived']",
at: datetime,
external_ref: str | None = None,
status_scope: ReportingStatusScope | None = None) ‑> ReportingIssueLifecycle
Expand source code
async def set_issue_state(
    self,
    *,
    issue_key: str,
    account_id: str,
    state: Literal["acknowledged", "waived"],
    at: datetime,
    external_ref: str | None = None,
    status_scope: ReportingStatusScope | None = None,
) -> ReportingIssueLifecycle:
    if state not in {"acknowledged", "waived"}:
        raise LedgerConflictError(
            "ISSUE_STATE_NOT_OPERATOR_SETTABLE",
            f"issue_state {state!r} is not settable by an operator. 'resolved' is "
            "reachable only when the condition actually clears -- the projection "
            "retires it -- because a seller must not retire a mismatch out of a "
            "degraded projection while the statement that caused it is still the "
            "consumer's current leaf",
        )
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, account_id)
        live = await self._live_issue(connection, issue_key, account_id)
        if live is None:
            raise LedgerConflictError(
                "ISSUE_NOT_OPEN",
                f"no open issue {issue_key!r} for this account; a retired issue cannot be "
                "reopened, and a recurrence gets a new occurrence",
            )
        check_issue_state_transition(live.issue_state, state)
        if state == "waived" and live.issue_state != "waived":
            from adcp.reporting.ledger.status_projection import bind_mismatch_waiver
            from adcp.reporting.ledger.status_snapshot import read_snapshot_on

            snapshot = await read_snapshot_on(
                connection,
                account_id=account_id,
                as_of=_utc(at),
                include_issue_scopes=await self._issue_scope_storage_on(connection),
            )
            live = bind_mismatch_waiver(snapshot, live)
            await _save_waiver_binding(connection, live)
        waived_at = (
            live.retired_at
            if live.waived_reporting_status_id is not None and live.retired_at is not None
            else _utc(at)
        )
        await connection.execute(
            "UPDATE reporting_issue_lifecycle"
            " SET issue_state = %s,"
            "     external_ref = COALESCE(%s, external_ref),"
            "     retired_at = CASE WHEN %s = 'waived' THEN %s ELSE retired_at END"
            " WHERE account_id = %s AND issue_key = %s AND generation = %s",
            (
                state,
                external_ref,
                state,
                waived_at,
                account_id,
                issue_key,
                live.generation,
            ),
        )
        refreshed = await self._issue_row(connection, issue_key, account_id, live.generation)
        assert refreshed is not None
        # Derive the no-op from the resulting row rather than predicting
        # it: an idempotent re-acknowledge changes nothing and enqueues
        # nothing, while anything that does move retained evidence stays
        # reconstructable for the projector.
        await self._dirty_issue(connection, refreshed, status_scope, live)
        return refreshed
async def set_revision_readable(self, *, account_id: str, reporting_revision_id: str, readable: bool) ‑> None
Expand source code
async def set_revision_readable(
    self, *, account_id: str, reporting_revision_id: str, readable: bool
) -> None:
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, account_id)
        existing = await (
            await connection.execute(
                "SELECT readable, reporting_obligation_id FROM reporting_revisions"
                " WHERE account_id = %s AND reporting_revision_id = %s FOR UPDATE",
                (account_id, reporting_revision_id),
            )
        ).fetchone()
        if existing is None:
            raise LedgerConflictError("REVISION_NOT_FOUND", "no such revision for this account")
        if existing[0] == readable:
            return
        await connection.execute(
            "UPDATE reporting_revisions SET readable = %s"
            " WHERE account_id = %s AND reporting_revision_id = %s",
            (readable, account_id, reporting_revision_id),
        )
        if self._notifications_enabled:
            obligation = await (
                await connection.execute(
                    f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # nosec B608
                    " WHERE account_id = %s AND reporting_obligation_id = %s",
                    (account_id, existing[1]),
                )
            ).fetchone()
            assert obligation is not None
            await self._dirty_status(
                connection,
                ReportingStatusScope.for_obligation(_obligation_from_row(obligation)),
                "readability",
                ReportingStatusEvidence(
                    "revision", reporting_revision_id, readable=existing[0]
                ),
                ReportingStatusEvidence("revision", reporting_revision_id, readable=readable),
            )
async def transaction(self) ‑> AsyncIterator[PgReportingLedgerStore]
Expand source code
@asynccontextmanager
async def transaction(self) -> AsyncIterator[PgReportingLedgerStore]:
    """Group source operations into one committed status boundary.

    Pool ownership remains with the adopter. Nested turns are savepoints;
    callers acquire accounts in canonical order when touching several.
    """
    async with self._connection() as connection, connection.transaction():
        token = _BOUND_CONNECTION.set((self._pool, asyncio.current_task(), connection))
        try:
            yield self
        finally:
            _BOUND_CONNECTION.reset(token)

Group source operations into one committed status boundary.

Pool ownership remains with the adopter. Nested turns are savepoints; callers acquire accounts in canonical order when touching several.

class PgReportingReconciliationStore (*,
pool: AsyncConnectionPool,
clock: Callable[[], datetime] | None = None,
notifications: bool = False)
Expand source code
class PgReportingReconciliationStore(PgReportingLedgerStore, _ReconciliationOperations):
    """Evidence and receipt heads commit with the feed, including autocommit pools.

    All state decisions run under the same per-account transaction lock as Core
    writes. Receipt replacement additionally uses a conditional head update;
    accepted heads and immutable evidence are protected by database triggers.
    """

    async def _commit(self, record: RecordT) -> tuple[RecordT, bool]:
        try:
            return await self._commit_record(record)
        except Exception as error:
            # A concurrent/raw SQL writer must not turn a constraint detail
            # (which may include an entire row) into a provider-payload echo.
            if not str(getattr(error, "sqlstate", "")).startswith("23"):
                raise
        unavailable()

    async def _commit_record(self, record: RecordT) -> tuple[RecordT, bool]:
        candidate = cast(RecordT, decode_record(payload(record)))
        who = principal(candidate)
        async with self._connection() as connection:
            async with connection.transaction():
                await self._lock_account(connection, who.account_id)
                return await self._commit_record_on(connection, candidate)

    async def _commit_record_on(
        self, connection: Any, record: RecordT, *, notify: bool = True, dirty: bool = True
    ) -> tuple[RecordT, bool]:
        """Connection-bound primitive. Caller holds the account transaction lock.

        Materializer finish uses this exact connection for evidence, caller
        feed, projection dirty work and its acknowledgment.
        No connection is acquired and no external code runs here.

        ``notify`` and ``dirty`` are independent: suppressing a readiness event
        must never also drop the projection work that an ordinary public write
        has always produced.
        """
        candidate = decode_record(payload(record))
        who = principal(candidate)
        records = await self._records(connection, who)
        existing = replay(candidate, records)
        if existing is not None:
            return cast(RecordT, existing), False
        context = await self._delivery_context(connection, candidate)
        now = await self._delivery_time_on(connection, candidate)
        stored = validate_transition(candidate, records, context, now)
        await self._insert(connection, stored)
        await self._append_reconciliation_change(connection, stored)
        if (notify or dirty) and self._notifications_enabled:
            from adcp.reporting.ledger.notification_events import (
                delivery_dirty,
                materialization_event,
            )

            if notify:
                event = materialization_event(
                    stored,
                    records,
                    context.obligation,
                    context.revision,
                    context.configuration,
                    now,
                )
                if event is not None:
                    await self._record_notification(connection, event)
            if dirty:
                scope, reason, evidence = delivery_dirty(stored, context.obligation)
                await self._dirty_status(connection, scope, reason, after=evidence)
        return cast(RecordT, stored), True

    async def _delivery_time_on(self, connection: Any, record: ReportingDeliveryRecord) -> datetime:
        """Connection-bound clock seam; old public clock overrides remain supported."""
        if self._clock is not None:
            return self._clock()
        time_row = await (await connection.execute("SELECT clock_timestamp()")).fetchone()
        assert time_row is not None
        return cast(datetime, time_row[0])

    async def _append_reconciliation_change(
        self, connection: Any, record: ReportingDeliveryRecord
    ) -> None:
        who = principal(record)
        row = await (
            await connection.execute(
                "INSERT INTO reporting_reconciliation_heads (account_id, consumer_id, max_sequence)"
                " VALUES (%s, %s, 1) ON CONFLICT (account_id, consumer_id) DO UPDATE"
                " SET max_sequence = reporting_reconciliation_heads.max_sequence + 1"
                " RETURNING max_sequence",
                (who.account_id, who.consumer_id),
            )
        ).fetchone()
        assert row is not None
        await connection.execute(
            "INSERT INTO reporting_reconciliation_changes"
            " (account_id, consumer_id, seq, namespace, record_id, record_kind,"
            " change_id, content_sha256, committed_at)"
            " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, COALESCE(%s, clock_timestamp()))",
            (
                who.account_id,
                who.consumer_id,
                row[0],
                *record_identity(record),
                record.kind,
                change_id(record),
                fingerprint(record),
                self._clock() if self._clock is not None else None,
            ),
        )

    async def _validate_feed(
        self, connection: Any, who: ReportingDeliveryPrincipal
    ) -> tuple[int, datetime]:
        # Scope both sides before the join, count, or boundary. A missing/moved
        # record or feed row must fail, never disappear through an inner join.
        # The payload digest scan covers the caller's whole retained graph, not
        # just the rows a requested filter happens to select: a single retained
        # payload that disagrees with its fingerprint must fail every page.
        # The joined SQL fragment is constant; every caller value is parameterized.
        row = await (
            await connection.execute(
                "SELECT count(c.seq), COALESCE(max(c.seq), 0),"  # nosec B608
                " COALESCE((SELECT max_sequence FROM reporting_reconciliation_heads"
                " WHERE account_id = %s AND consumer_id = %s), 0),"
                " COALESCE(bool_or(c.seq IS NULL OR r.record_id IS NULL), false),"
                " COALESCE((SELECT bool_or(x.content_sha256"
                " <> reporting_payload_sha256(x.payload))"
                " FROM reporting_reconciliation_records x"
                " WHERE x.account_id = %s AND x.consumer_id = %s), false), clock_timestamp()"
                " FROM (SELECT * FROM reporting_reconciliation_changes"
                " WHERE account_id = %s AND consumer_id = %s) c"
                " FULL JOIN (SELECT * FROM reporting_reconciliation_records"
                " WHERE account_id = %s AND consumer_id = %s) r ON " + _FEED_JOIN,
                (who.account_id, who.consumer_id) * 4,
            )
        ).fetchone()
        assert row is not None
        if row[0] != row[1] or row[1] != row[2] or row[3] or row[4]:
            fail("REPORTING_HISTORY_CORRUPT")
        return row[2], row[5]

    async def _records(
        self, connection: Any, who: ReportingDeliveryPrincipal, maximum: int | None = None
    ) -> tuple[ReportingDeliveryRecord, ...]:
        await self._validate_feed(connection, who)
        return tuple(item.record for item in await self._changes(connection, who, maximum))

    async def _changes(
        self,
        connection: Any,
        who: ReportingDeliveryPrincipal,
        maximum: int | None = None,
        *,
        after: int = 0,
        limit: int | None = None,
        filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
    ) -> tuple[ReportingReconciliationChange, ...]:
        rows = await (
            await connection.execute(
                "SELECT r.payload, r.content_sha256, c.seq, r.change_id, "
                f"{_SELECT_IDENTITY} FROM reporting_reconciliation_changes c"  # noqa: S608  # nosec B608
                f" JOIN reporting_reconciliation_records r ON {_FEED_JOIN}"
                " WHERE c.account_id = %s AND c.consumer_id = %s"
                " AND (%s::bigint IS NULL OR c.seq <= %s::bigint) AND c.seq > %s"
                f" AND {_FEED_FILTER} ORDER BY c.seq LIMIT %s",
                (
                    who.account_id,
                    who.consumer_id,
                    maximum,
                    maximum,
                    after,
                    *_filter_params(filters),
                    limit,
                ),
            )
        ).fetchall()
        records = tuple(decode_record(row[0]) for row in rows)
        if any(
            principal(record) != who
            or fingerprint(record) != row[1]
            or row[2] is None
            or change_id(record) != row[3]
            or storage_identity(record) != tuple(row[4:])
            for record, row in zip(records, rows)
        ):
            fail("REPORTING_HISTORY_CORRUPT")
        return tuple(
            ReportingReconciliationChange(row[2], record) for row, record in zip(rows, records)
        )

    async def _change_count(
        self,
        connection: Any,
        caller: ReportingDeliveryPrincipal,
        after: int,
        maximum: int,
        filters: ReportingReconciliationFilter,
    ) -> int:
        row = await (
            await connection.execute(
                "SELECT count(*) FROM reporting_reconciliation_changes c"
                f" JOIN reporting_reconciliation_records r ON {_FEED_JOIN}"  # noqa: S608  # nosec B608
                " WHERE c.account_id = %s AND c.consumer_id = %s AND c.seq > %s AND c.seq <= %s"
                f" AND {_FEED_FILTER}",
                (caller.account_id, caller.consumer_id, after, maximum, *_filter_params(filters)),
            )
        ).fetchone()
        assert row is not None
        return int(row[0])

    async def _delivery_context(
        self, connection: Any, record: ReportingDeliveryRecord
    ) -> DeliveryContext:
        who = principal(record)
        configuration = None
        if isinstance(record, ReportingDestinationBinding) or self._notifications_enabled:
            generation = (
                record.generation_key
                if isinstance(record, ReportingDestinationBinding)
                else record.scope.generation_key
            )
            row = await (
                await connection.execute(
                    "SELECT delivery_config_id, delivery_config_version, account_id,"
                    " report_definition_id, reporting_profile, feed_purpose, required_finality,"
                    " account_timezone, schedule, media_buy_ids, activated_at, deactivated_at,"
                    " automated_recovery_seconds, status_retention_days, definition,"
                    " authoritative_party, consumer_id, quarantined"
                    " FROM reporting_configurations WHERE account_id = %s"
                    " AND consumer_id = %s AND delivery_config_id = %s AND delivery_co"
                    "nfig_version = %s",
                    (
                        who.account_id,
                        who.consumer_id,
                        generation.delivery_config_id,
                        generation.delivery_config_version,
                    ),
                )
            ).fetchone()
            configuration = _configuration_from_row(row) if row else None
        if isinstance(record, ReportingDestinationBinding):
            return DeliveryContext(configuration=configuration)
        obligation_row = await (
            await connection.execute(
                f"SELECT {_OBLIGATION_COLUMNS} FROM reporting_obligations"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND consumer_id = %s AND reporting_obligation_id = %s",
                (who.account_id, who.consumer_id, record.scope.reporting_obligation_id),
            )
        ).fetchone()
        revision_id = getattr(record, "reporting_revision_id", None)
        if isinstance(record, ReportingAdjustmentReceiptRecord):
            revision_id = record.adjusts_reporting_revision_id
        revision_row = await (
            await connection.execute(
                f"SELECT {_REVISION_COLUMNS} FROM reporting_revisions"  # noqa: S608  # nosec B608
                " WHERE account_id = %s AND reporting_revision_id = %s",
                (who.account_id, revision_id),
            )
        ).fetchone()
        adjustment_row = None
        if isinstance(record, ReportingAdjustmentReceiptRecord):
            adjustment_row = await (
                await connection.execute(
                    f"SELECT {_ADJUSTMENT_COLUMNS} FROM reporting_adjustments"  # noqa: S608  # nosec B608
                    " WHERE account_id = %s AND reporting_adjustment_id = %s",
                    (who.account_id, record.reporting_adjustment_id),
                )
            ).fetchone()
        return DeliveryContext(
            configuration=configuration,
            obligation=_obligation_from_row(obligation_row) if obligation_row else None,
            revision=_revision_from_row(revision_row) if revision_row else None,
            adjustment=_adjustment_from_row(adjustment_row) if adjustment_row else None,
        )

    async def _insert(self, connection: Any, record: ReportingDeliveryRecord) -> None:
        await connection.execute(
            "INSERT INTO reporting_reconciliation_records"
            " (account_id, consumer_id, namespace, record_id, record_kind, delivery_config_id,"
            " delivery_config_version, reporting_obligation_id, reporting_revision_id,"
            " reporting_materialization_id, reporting_adjustment_id, attempt_number,"
            " receipt_chain_key, receipt_status, supersedes_receipt_id, payload,"
            " content_sha256, change_id)"
            " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,"
            " %s::jsonb, %s, %s)",
            (
                *storage_identity(record),
                _json(payload(record)),
                fingerprint(record),
                change_id(record),
            ),
        )

    async def read_reconciliation_snapshot(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        boundary: ReportingReconciliationSnapshotToken | None = None,
    ) -> ReportingReconciliationSnapshot:
        requested = boundary is not None
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, caller.account_id)
            maximum, now = await self._validate_feed(connection, caller)
            if boundary is None:
                boundary = change_boundary(
                    caller, maximum, self._clock() if self._clock is not None else now
                )
            if (
                type(boundary) is not ReportingReconciliationSnapshotToken
                or boundary.caller != caller
                or boundary.min_sequence != 0
                or boundary.filters != ReportingReconciliationFilter()
            ):
                unavailable()
            validate_boundary(caller, boundary, maximum)
            records = tuple(
                item.record
                for item in await self._changes(connection, caller, boundary.max_sequence)
            )
            if len(records) != boundary.total_count:
                # See _boundary_unavailable: the caller's token, not the store.
                _boundary_unavailable(requested)
        return ReportingReconciliationSnapshot(caller, boundary, records)

    async def read_reconciliation_changes(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        changes_after: ReportingReconciliationCheckpoint | None = None,
        cursor: ReportingReconciliationCursor | None = None,
        limit: int = 100,
        filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
    ) -> ReportingReconciliationPage:
        after, boundary, last_key = read_position(caller, changes_after, cursor, limit, filters)
        async with self._connection() as connection, connection.transaction():
            await self._lock_account(connection, caller.account_id)
            maximum, now = await self._validate_feed(connection, caller)
            if boundary is None:
                count = await self._change_count(connection, caller, after, maximum, filters)
                boundary = change_boundary(
                    caller,
                    maximum,
                    self._clock() if self._clock is not None else now,
                    after=after,
                    total_count=count,
                    filters=filters,
                )
            validate_boundary(caller, boundary, maximum)
            if last_key is not None:
                last = await self._changes(
                    connection, caller, after, after=after - 1, limit=1, filters=filters
                )
                if not last or change_id(last[0].record) != last_key:
                    raise LedgerConflictError(
                        "INVALID_CHECKPOINT", "reconciliation key is unavailable"
                    )
                count = await self._change_count(
                    connection, caller, boundary.min_sequence, boundary.max_sequence, filters
                )
                if count != boundary.total_count:
                    _boundary_unavailable(True)
            changes = await self._changes(
                connection,
                caller,
                boundary.max_sequence,
                after=after,
                limit=limit + 1,
                filters=filters,
            )
        return change_page(caller, boundary, after, changes, limit)

Evidence and receipt heads commit with the feed, including autocommit pools.

All state decisions run under the same per-account transaction lock as Core writes. Receipt replacement additionally uses a conditional head update; accepted heads and immutable evidence are protected by database triggers.

Ancestors

Subclasses

Methods

async def read_reconciliation_changes(self,
*,
caller: ReportingDeliveryPrincipal,
changes_after: ReportingReconciliationCheckpoint | None = None,
cursor: ReportingReconciliationCursor | None = None,
limit: int = 100,
filters: ReportingReconciliationFilter = ReportingReconciliationFilter(record_kinds=(), reporting_obligation_id=None)) ‑> ReportingReconciliationPage
Expand source code
async def read_reconciliation_changes(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    changes_after: ReportingReconciliationCheckpoint | None = None,
    cursor: ReportingReconciliationCursor | None = None,
    limit: int = 100,
    filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
) -> ReportingReconciliationPage:
    after, boundary, last_key = read_position(caller, changes_after, cursor, limit, filters)
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, caller.account_id)
        maximum, now = await self._validate_feed(connection, caller)
        if boundary is None:
            count = await self._change_count(connection, caller, after, maximum, filters)
            boundary = change_boundary(
                caller,
                maximum,
                self._clock() if self._clock is not None else now,
                after=after,
                total_count=count,
                filters=filters,
            )
        validate_boundary(caller, boundary, maximum)
        if last_key is not None:
            last = await self._changes(
                connection, caller, after, after=after - 1, limit=1, filters=filters
            )
            if not last or change_id(last[0].record) != last_key:
                raise LedgerConflictError(
                    "INVALID_CHECKPOINT", "reconciliation key is unavailable"
                )
            count = await self._change_count(
                connection, caller, boundary.min_sequence, boundary.max_sequence, filters
            )
            if count != boundary.total_count:
                _boundary_unavailable(True)
        changes = await self._changes(
            connection,
            caller,
            boundary.max_sequence,
            after=after,
            limit=limit + 1,
            filters=filters,
        )
    return change_page(caller, boundary, after, changes, limit)
async def read_reconciliation_snapshot(self,
*,
caller: ReportingDeliveryPrincipal,
boundary: ReportingReconciliationSnapshotToken | None = None) ‑> ReportingReconciliationSnapshot
Expand source code
async def read_reconciliation_snapshot(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    boundary: ReportingReconciliationSnapshotToken | None = None,
) -> ReportingReconciliationSnapshot:
    requested = boundary is not None
    async with self._connection() as connection, connection.transaction():
        await self._lock_account(connection, caller.account_id)
        maximum, now = await self._validate_feed(connection, caller)
        if boundary is None:
            boundary = change_boundary(
                caller, maximum, self._clock() if self._clock is not None else now
            )
        if (
            type(boundary) is not ReportingReconciliationSnapshotToken
            or boundary.caller != caller
            or boundary.min_sequence != 0
            or boundary.filters != ReportingReconciliationFilter()
        ):
            unavailable()
        validate_boundary(caller, boundary, maximum)
        records = tuple(
            item.record
            for item in await self._changes(connection, caller, boundary.max_sequence)
        )
        if len(records) != boundary.total_count:
            # See _boundary_unavailable: the caller's token, not the store.
            _boundary_unavailable(requested)
    return ReportingReconciliationSnapshot(caller, boundary, records)

Inherited members

class ProducerOfferings (snapshot_offering_id: str | None = None,
official_offering_id: str | None = None,
publication_namespace: str = 'reporting-source:default',
requested_metrics: tuple[str, ...] = ('impressions', 'spend'),
requested_dimensions: tuple[str, ...] = (),
currency: str = 'USD',
source_scope: dict[str, Any] = <factory>,
slice_timeout: timedelta = datetime.timedelta(seconds=600))
Expand source code
@dataclass(frozen=True)
class ProducerOfferings:
    """Which source offering serves which finality for one configuration.

    Snapshot and official source publications use different offerings. A
    source-declared settling policy may use them sequentially for one ledger
    obligation, so the producer needs both named explicitly rather than
    inferring one from the other.
    """

    snapshot_offering_id: str | None = None
    official_offering_id: str | None = None
    publication_namespace: str = "reporting-source:default"
    requested_metrics: tuple[str, ...] = ("impressions", "spend")
    requested_dimensions: tuple[str, ...] = ()
    currency: str = "USD"
    source_scope: dict[str, Any] = field(default_factory=dict)
    slice_timeout: timedelta = timedelta(minutes=10)

    def offering_for(self, finality: str) -> str | None:
        return self.official_offering_id if finality == "official" else self.snapshot_offering_id

Which source offering serves which finality for one configuration.

Snapshot and official source publications use different offerings. A source-declared settling policy may use them sequentially for one ledger obligation, so the producer needs both named explicitly rather than inferring one from the other.

Instance variables

var currency : str
var official_offering_id : str | None
var publication_namespace : str
var requested_dimensions : tuple[str, ...]
var requested_metrics : tuple[str, ...]
var slice_timeout : datetime.timedelta
var snapshot_offering_id : str | None
var source_scope : dict[str, typing.Any]

Methods

def offering_for(self, finality: str) ‑> str | None
Expand source code
def offering_for(self, finality: str) -> str | None:
    return self.official_offering_id if finality == "official" else self.snapshot_offering_id
class ReportingAdjustmentReceiptRecord (scope: ReportingDeliveryScope,
reporting_receipt_id: str,
reporting_adjustment_id: str,
adjusts_reporting_revision_id: str,
status: ReceiptStatus,
observed_adjustment_sha256: str,
observed_at: datetime,
supersedes_reporting_receipt_id: str | None = None,
rejection_codes: tuple[str, ...] = (),
received_at: datetime | None = None,
*,
kind: "Literal['adjustment_receipt']" = 'adjustment_receipt')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)

ReportingAdjustmentReceiptRecord(scope: 'ReportingDeliveryScope', reporting_receipt_id: 'str', reporting_adjustment_id: 'str', adjusts_reporting_revision_id: 'str', status: 'ReceiptStatus', observed_adjustment_sha256: 'str', observed_at: 'datetime', supersedes_reporting_receipt_id: 'str | None' = None, rejection_codes: 'tuple[str, …]' = (), received_at: 'datetime | None' = None, *, kind: "Literal['adjustment_receipt']" = 'adjustment_receipt')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var adjusts_reporting_revision_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
prop key : ReportingReceiptKey
Expand source code
@property
def key(self) -> ReportingReceiptKey:
    return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var kind : Literal['adjustment_receipt']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_adjustment_sha256 : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var received_at : datetime.datetime | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var rejection_codes : tuple[str, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var reporting_adjustment_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var reporting_receipt_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var scope : ReportingDeliveryScope
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var status : Literal['accepted', 'rejected']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var supersedes_reporting_receipt_id : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingAdjustmentReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_adjustment_id: str
    adjusts_reporting_revision_id: str
    status: ReceiptStatus
    observed_adjustment_sha256: str
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["adjustment_receipt"] = field(default="adjustment_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_adjustment_id, maximum=255)
        reporting_identifier(self.adjusts_reporting_revision_id, maximum=255)
        sha256_value(self.observed_adjustment_sha256)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
class ReportingAdjustmentRecord (reporting_adjustment_id: str,
account_id: str,
adjusts_reporting_revision_id: str,
reason_code: "Literal['invalid_traffic', 'late_attribution', 'source_correction', 'mapping_correction', 'commercial_adjustment', 'other']",
accounting_period_start: datetime,
accounting_period_end: datetime,
control_total_deltas: tuple[tuple[str, str], ...],
correction_observed_at: datetime,
created_at: datetime,
reason_detail: str | None = None,
managed_control_total_deltas: tuple[ReportingControlTotalRecord, ...] | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingAdjustmentRecord:
    """An immutable post-official accounting correction.

    An official revision is terminal.  When the source corrects it, the
    correction lands here as a signed delta against an open accounting period,
    preserving the original invoice-to-revision binding rather than rewriting
    it.
    """

    reporting_adjustment_id: str
    account_id: str
    adjusts_reporting_revision_id: str
    reason_code: Literal[
        "invalid_traffic",
        "late_attribution",
        "source_correction",
        "mapping_correction",
        "commercial_adjustment",
        "other",
    ]
    accounting_period_start: datetime
    accounting_period_end: datetime
    control_total_deltas: tuple[tuple[str, str], ...]
    correction_observed_at: datetime
    created_at: datetime
    reason_detail: str | None = None
    managed_control_total_deltas: tuple[ReportingControlTotalRecord, ...] | None = None

    def __post_init__(self) -> None:
        object.__setattr__(
            self, "control_total_deltas", tuple(tuple(item) for item in self.control_total_deltas)
        )
        if self.managed_control_total_deltas is not None:
            object.__setattr__(
                self,
                "managed_control_total_deltas",
                freeze_control_totals(self.managed_control_total_deltas, self.control_total_deltas),
            )

An immutable post-official accounting correction.

An official revision is terminal. When the source corrects it, the correction lands here as a signed delta against an open accounting period, preserving the original invoice-to-revision binding rather than rewriting it.

Instance variables

var account_id : str
var accounting_period_end : datetime.datetime
var accounting_period_start : datetime.datetime
var adjusts_reporting_revision_id : str
var control_total_deltas : tuple[tuple[str, str], ...]
var correction_observed_at : datetime.datetime
var created_at : datetime.datetime
var managed_control_total_deltas : tuple[ReportingControlTotalRecord, ...] | None
var reason_code : Literal['invalid_traffic', 'late_attribution', 'source_correction', 'mapping_correction', 'commercial_adjustment', 'other']
var reason_detail : str | None
var reporting_adjustment_id : str
class ReportingCaller (*args, **kwargs)
Expand source code
class ReportingCaller(Protocol):
    """Authenticated account/caller coordinates supplied by a trusted transport."""

    @property
    def account_id(self) -> str: ...

    @property
    def consumer_id(self) -> str: ...

Authenticated account/caller coordinates supplied by a trusted transport.

Ancestors

  • typing.Protocol
  • typing.Generic

Instance variables

prop account_id : str
Expand source code
@property
def account_id(self) -> str: ...
prop consumer_id : str
Expand source code
@property
def consumer_id(self) -> str: ...
class ReportingCanonicalDigest (value: str,
canonicalization_id: str,
canonicalization_uri: str,
canonicalization_sha256: str)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingCanonicalDigest:
    """A trusted publisher's logical-row digest under a pinned managed contract.

    This is distinct from Core's fixed ``revision_content_sha256``. A future
    managed publisher must verify the pinned contract and compute this value
    before destination work; destination output cannot establish the expectation.
    """

    value: str
    canonicalization_id: str
    canonicalization_uri: str
    canonicalization_sha256: str

    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if any(
            type(value) is not str
            for value in (
                self.value,
                self.canonicalization_id,
                self.canonicalization_uri,
                self.canonicalization_sha256,
            )
        ):
            raise ValueError("canonical evidence requires immutable string values")
        sha256_value(self.value)
        sha256_value(self.canonicalization_sha256)
        _public_text(self.canonicalization_id, maximum=128)
        uri = self.canonicalization_uri
        try:
            parsed = urlsplit(uri)
            valid = (
                parsed.scheme == "https"
                and parsed.hostname is not None
                and re.fullmatch(r"(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}", parsed.hostname)
                and not parsed.username
                and not parsed.password
                and not parsed.query
                and not parsed.fragment
                and uri.isascii()
                and not re.search(r"[\s\\%]", uri)
                and not re.search(r"(?i)(token|secret|signature|credential)", uri)
            )
        except (TypeError, ValueError):
            valid = False
        if not valid:
            raise ValueError("canonicalization requires a public HTTPS contract URI")

    def to_wire(self) -> dict[str, str]:
        return {
            "algorithm": "sha256",
            "value": self.value,
            "canonicalization_id": self.canonicalization_id,
            "canonicalization_uri": self.canonicalization_uri,
            "canonicalization_sha256": self.canonicalization_sha256,
        }

    @classmethod
    def from_wire(cls, value: object) -> ReportingCanonicalDigest:
        if (
            not isinstance(value, dict)
            or set(value)
            != {
                "algorithm",
                "value",
                "canonicalization_id",
                "canonicalization_uri",
                "canonicalization_sha256",
            }
            or value.get("algorithm") != "sha256"
        ):
            raise ValueError("invalid retained canonical evidence")
        return cls(
            value["value"],
            value["canonicalization_id"],
            value["canonicalization_uri"],
            value["canonicalization_sha256"],
        )

A trusted publisher's logical-row digest under a pinned managed contract.

This is distinct from Core's fixed revision_content_sha256(). A future managed publisher must verify the pinned contract and compute this value before destination work; destination output cannot establish the expectation.

Static methods

def from_wire(value: object) ‑> ReportingCanonicalDigest

Instance variables

var canonicalization_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingCanonicalDigest:
    """A trusted publisher's logical-row digest under a pinned managed contract.

    This is distinct from Core's fixed ``revision_content_sha256``. A future
    managed publisher must verify the pinned contract and compute this value
    before destination work; destination output cannot establish the expectation.
    """

    value: str
    canonicalization_id: str
    canonicalization_uri: str
    canonicalization_sha256: str

    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if any(
            type(value) is not str
            for value in (
                self.value,
                self.canonicalization_id,
                self.canonicalization_uri,
                self.canonicalization_sha256,
            )
        ):
            raise ValueError("canonical evidence requires immutable string values")
        sha256_value(self.value)
        sha256_value(self.canonicalization_sha256)
        _public_text(self.canonicalization_id, maximum=128)
        uri = self.canonicalization_uri
        try:
            parsed = urlsplit(uri)
            valid = (
                parsed.scheme == "https"
                and parsed.hostname is not None
                and re.fullmatch(r"(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}", parsed.hostname)
                and not parsed.username
                and not parsed.password
                and not parsed.query
                and not parsed.fragment
                and uri.isascii()
                and not re.search(r"[\s\\%]", uri)
                and not re.search(r"(?i)(token|secret|signature|credential)", uri)
            )
        except (TypeError, ValueError):
            valid = False
        if not valid:
            raise ValueError("canonicalization requires a public HTTPS contract URI")

    def to_wire(self) -> dict[str, str]:
        return {
            "algorithm": "sha256",
            "value": self.value,
            "canonicalization_id": self.canonicalization_id,
            "canonicalization_uri": self.canonicalization_uri,
            "canonicalization_sha256": self.canonicalization_sha256,
        }

    @classmethod
    def from_wire(cls, value: object) -> ReportingCanonicalDigest:
        if (
            not isinstance(value, dict)
            or set(value)
            != {
                "algorithm",
                "value",
                "canonicalization_id",
                "canonicalization_uri",
                "canonicalization_sha256",
            }
            or value.get("algorithm") != "sha256"
        ):
            raise ValueError("invalid retained canonical evidence")
        return cls(
            value["value"],
            value["canonicalization_id"],
            value["canonicalization_uri"],
            value["canonicalization_sha256"],
        )
var canonicalization_sha256 : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingCanonicalDigest:
    """A trusted publisher's logical-row digest under a pinned managed contract.

    This is distinct from Core's fixed ``revision_content_sha256``. A future
    managed publisher must verify the pinned contract and compute this value
    before destination work; destination output cannot establish the expectation.
    """

    value: str
    canonicalization_id: str
    canonicalization_uri: str
    canonicalization_sha256: str

    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if any(
            type(value) is not str
            for value in (
                self.value,
                self.canonicalization_id,
                self.canonicalization_uri,
                self.canonicalization_sha256,
            )
        ):
            raise ValueError("canonical evidence requires immutable string values")
        sha256_value(self.value)
        sha256_value(self.canonicalization_sha256)
        _public_text(self.canonicalization_id, maximum=128)
        uri = self.canonicalization_uri
        try:
            parsed = urlsplit(uri)
            valid = (
                parsed.scheme == "https"
                and parsed.hostname is not None
                and re.fullmatch(r"(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}", parsed.hostname)
                and not parsed.username
                and not parsed.password
                and not parsed.query
                and not parsed.fragment
                and uri.isascii()
                and not re.search(r"[\s\\%]", uri)
                and not re.search(r"(?i)(token|secret|signature|credential)", uri)
            )
        except (TypeError, ValueError):
            valid = False
        if not valid:
            raise ValueError("canonicalization requires a public HTTPS contract URI")

    def to_wire(self) -> dict[str, str]:
        return {
            "algorithm": "sha256",
            "value": self.value,
            "canonicalization_id": self.canonicalization_id,
            "canonicalization_uri": self.canonicalization_uri,
            "canonicalization_sha256": self.canonicalization_sha256,
        }

    @classmethod
    def from_wire(cls, value: object) -> ReportingCanonicalDigest:
        if (
            not isinstance(value, dict)
            or set(value)
            != {
                "algorithm",
                "value",
                "canonicalization_id",
                "canonicalization_uri",
                "canonicalization_sha256",
            }
            or value.get("algorithm") != "sha256"
        ):
            raise ValueError("invalid retained canonical evidence")
        return cls(
            value["value"],
            value["canonicalization_id"],
            value["canonicalization_uri"],
            value["canonicalization_sha256"],
        )
var canonicalization_uri : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingCanonicalDigest:
    """A trusted publisher's logical-row digest under a pinned managed contract.

    This is distinct from Core's fixed ``revision_content_sha256``. A future
    managed publisher must verify the pinned contract and compute this value
    before destination work; destination output cannot establish the expectation.
    """

    value: str
    canonicalization_id: str
    canonicalization_uri: str
    canonicalization_sha256: str

    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if any(
            type(value) is not str
            for value in (
                self.value,
                self.canonicalization_id,
                self.canonicalization_uri,
                self.canonicalization_sha256,
            )
        ):
            raise ValueError("canonical evidence requires immutable string values")
        sha256_value(self.value)
        sha256_value(self.canonicalization_sha256)
        _public_text(self.canonicalization_id, maximum=128)
        uri = self.canonicalization_uri
        try:
            parsed = urlsplit(uri)
            valid = (
                parsed.scheme == "https"
                and parsed.hostname is not None
                and re.fullmatch(r"(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}", parsed.hostname)
                and not parsed.username
                and not parsed.password
                and not parsed.query
                and not parsed.fragment
                and uri.isascii()
                and not re.search(r"[\s\\%]", uri)
                and not re.search(r"(?i)(token|secret|signature|credential)", uri)
            )
        except (TypeError, ValueError):
            valid = False
        if not valid:
            raise ValueError("canonicalization requires a public HTTPS contract URI")

    def to_wire(self) -> dict[str, str]:
        return {
            "algorithm": "sha256",
            "value": self.value,
            "canonicalization_id": self.canonicalization_id,
            "canonicalization_uri": self.canonicalization_uri,
            "canonicalization_sha256": self.canonicalization_sha256,
        }

    @classmethod
    def from_wire(cls, value: object) -> ReportingCanonicalDigest:
        if (
            not isinstance(value, dict)
            or set(value)
            != {
                "algorithm",
                "value",
                "canonicalization_id",
                "canonicalization_uri",
                "canonicalization_sha256",
            }
            or value.get("algorithm") != "sha256"
        ):
            raise ValueError("invalid retained canonical evidence")
        return cls(
            value["value"],
            value["canonicalization_id"],
            value["canonicalization_uri"],
            value["canonicalization_sha256"],
        )
var value : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingCanonicalDigest:
    """A trusted publisher's logical-row digest under a pinned managed contract.

    This is distinct from Core's fixed ``revision_content_sha256``. A future
    managed publisher must verify the pinned contract and compute this value
    before destination work; destination output cannot establish the expectation.
    """

    value: str
    canonicalization_id: str
    canonicalization_uri: str
    canonicalization_sha256: str

    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if any(
            type(value) is not str
            for value in (
                self.value,
                self.canonicalization_id,
                self.canonicalization_uri,
                self.canonicalization_sha256,
            )
        ):
            raise ValueError("canonical evidence requires immutable string values")
        sha256_value(self.value)
        sha256_value(self.canonicalization_sha256)
        _public_text(self.canonicalization_id, maximum=128)
        uri = self.canonicalization_uri
        try:
            parsed = urlsplit(uri)
            valid = (
                parsed.scheme == "https"
                and parsed.hostname is not None
                and re.fullmatch(r"(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}", parsed.hostname)
                and not parsed.username
                and not parsed.password
                and not parsed.query
                and not parsed.fragment
                and uri.isascii()
                and not re.search(r"[\s\\%]", uri)
                and not re.search(r"(?i)(token|secret|signature|credential)", uri)
            )
        except (TypeError, ValueError):
            valid = False
        if not valid:
            raise ValueError("canonicalization requires a public HTTPS contract URI")

    def to_wire(self) -> dict[str, str]:
        return {
            "algorithm": "sha256",
            "value": self.value,
            "canonicalization_id": self.canonicalization_id,
            "canonicalization_uri": self.canonicalization_uri,
            "canonicalization_sha256": self.canonicalization_sha256,
        }

    @classmethod
    def from_wire(cls, value: object) -> ReportingCanonicalDigest:
        if (
            not isinstance(value, dict)
            or set(value)
            != {
                "algorithm",
                "value",
                "canonicalization_id",
                "canonicalization_uri",
                "canonicalization_sha256",
            }
            or value.get("algorithm") != "sha256"
        ):
            raise ValueError("invalid retained canonical evidence")
        return cls(
            value["value"],
            value["canonicalization_id"],
            value["canonicalization_uri"],
            value["canonicalization_sha256"],
        )

Methods

def to_wire(self) ‑> dict[str, str]
Expand source code
def to_wire(self) -> dict[str, str]:
    return {
        "algorithm": "sha256",
        "value": self.value,
        "canonicalization_id": self.canonicalization_id,
        "canonicalization_uri": self.canonicalization_uri,
        "canonicalization_sha256": self.canonicalization_sha256,
    }
class ReportingConfiguration (delivery_config_id: str,
delivery_config_version: int,
account_id: str,
consumer_id: str,
report_definition_id: str,
reporting_profile: str,
feed_purpose: str,
schedule: ReportingScheduleSpec,
required_finality: ReportingFinality,
account_timezone: str = 'UTC',
activated_at: datetime | None = None,
deactivated_at: datetime | None = None,
media_buy_ids: tuple[str, ...] = (),
automated_recovery_window: timedelta = datetime.timedelta(seconds=21600),
status_retention_days: int = 400,
definition: ReportingDefinitionBinding | None = None,
authoritative_party: "Literal['seller', 'consumer']" = 'seller',
quarantined: bool = False)
Expand source code
@dataclass(frozen=True)
class ReportingConfiguration:
    """One accepted reporting configuration generation.

    A generation is immutable.  Campaign starts, stops, and configuration
    changes alter *future* obligations; they never alter past ones, because a
    buyer that retained this generation must be able to re-derive the same
    expectations from it years later.
    """

    delivery_config_id: str
    delivery_config_version: int
    account_id: str
    consumer_id: str
    report_definition_id: str
    reporting_profile: str
    feed_purpose: str
    schedule: ReportingScheduleSpec
    required_finality: ReportingFinality
    account_timezone: str = "UTC"
    activated_at: datetime | None = None
    deactivated_at: datetime | None = None
    media_buy_ids: tuple[str, ...] = ()
    automated_recovery_window: timedelta = timedelta(hours=6)
    status_retention_days: int = 400
    definition: ReportingDefinitionBinding | None = None
    # AdCP 3.2.0-rc.3 reserves this for the buyer-deposited billing revision
    # task scoped to a later minor. ``seller`` is the default and the only
    # value any 3.2 seller accepts; ``consumer`` is carried rather than
    # dropped so :meth:`ReportingLedgerStore.put_configuration` can reject it
    # with UNSUPPORTED_FEATURE. The spec forbids silently coercing it.
    authoritative_party: Literal["seller", "consumer"] = "seller"
    # Maintenance imports are read-only; recovery requires a new generation.
    quarantined: bool = False

    def __post_init__(self) -> None:
        principal_reference(self.account_id)
        consumer_reference(self.consumer_id)

    @property
    def generation_key(self) -> ReportingConfigurationGenerationKey:
        return ReportingConfigurationGenerationKey(
            account_id=self.account_id,
            consumer_id=self.consumer_id,
            delivery_config_id=self.delivery_config_id,
            delivery_config_version=self.delivery_config_version,
        )

One accepted reporting configuration generation.

A generation is immutable. Campaign starts, stops, and configuration changes alter future obligations; they never alter past ones, because a buyer that retained this generation must be able to re-derive the same expectations from it years later.

Instance variables

var account_id : str
var account_timezone : str
var activated_at : datetime.datetime | None
var authoritative_party : Literal['seller', 'consumer']
var automated_recovery_window : datetime.timedelta
var consumer_id : str
var deactivated_at : datetime.datetime | None
var definition : ReportingDefinitionBinding | None
var delivery_config_id : str
var delivery_config_version : int
var feed_purpose : str
prop generation_key : ReportingConfigurationGenerationKey
Expand source code
@property
def generation_key(self) -> ReportingConfigurationGenerationKey:
    return ReportingConfigurationGenerationKey(
        account_id=self.account_id,
        consumer_id=self.consumer_id,
        delivery_config_id=self.delivery_config_id,
        delivery_config_version=self.delivery_config_version,
    )
var media_buy_ids : tuple[str, ...]
var quarantined : bool
var report_definition_id : str
var reporting_profile : str
var required_finality : Literal['snapshot', 'official']
var schedule : ReportingScheduleSpec
var status_retention_days : int
class ReportingConfigurationGenerationKey (account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int)
Expand source code
@dataclass(frozen=True)
class ReportingConfigurationGenerationKey:
    """The caller/account-qualified identity of one accepted configuration generation.

    ``delivery_config_id`` is caller-selected and may be reused by another
    account. Use this value for lookups, joins, and leases rather than a tuple
    that could omit the account. It is immutable and hashable for use in maps.
    """

    account_id: str
    consumer_id: str
    delivery_config_id: str
    delivery_config_version: int

    def __post_init__(self) -> None:
        principal_reference(self.account_id)
        consumer_reference(self.consumer_id)

The caller/account-qualified identity of one accepted configuration generation.

delivery_config_id is caller-selected and may be reused by another account. Use this value for lookups, joins, and leases rather than a tuple that could omit the account. It is immutable and hashable for use in maps.

Instance variables

var account_id : str
var consumer_id : str
var delivery_config_id : str
var delivery_config_version : int
class ReportingControlTotalRecord (name: str,
value: str,
value_type: "Literal['integer', 'decimal']",
unit: str | None = None)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingControlTotalRecord:
    """An exact expected or observed wire total, with its declared type and unit."""

    name: str
    value: str
    value_type: Literal["integer", "decimal"]
    unit: str | None = None
    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if (
            any(type(value) is not str for value in (self.name, self.value, self.value_type))
            or (self.unit is not None and type(self.unit) is not str)
            or self.value_type not in {"integer", "decimal"}
        ):
            raise ValueError("control total evidence requires immutable typed values")
        reporting_identifier(self.name, maximum=128)
        if re.fullmatch(r"[A-Za-z][A-Za-z0-9_.:-]{0,127}", self.name) is None:
            raise ValueError("reporting total names require public metric identifiers")
        pattern = (
            r"-?(?:0|[1-9][0-9]*)"
            if self.value_type == "integer"
            else r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?"
        )
        if re.fullmatch(pattern, self.value) is None:
            raise ValueError("control totals require canonical numeric strings matching their type")
        if self.unit is not None:
            _public_text(self.unit, maximum=32)

    def to_wire(self) -> dict[str, str]:
        result = {"name": self.name, "value": self.value, "value_type": self.value_type}
        if self.unit is not None:
            result["unit"] = self.unit
        return result

    @classmethod
    def from_wire(cls, value: object) -> ReportingControlTotalRecord:
        if (
            not isinstance(value, dict)
            or not {"name", "value", "value_type"} <= set(value)
            or set(value) - {"name", "value", "value_type", "unit"}
        ):
            raise ValueError("invalid retained control total evidence")
        return cls(value["name"], value["value"], value["value_type"], value.get("unit"))

An exact expected or observed wire total, with its declared type and unit.

Static methods

def from_wire(value: object) ‑> ReportingControlTotalRecord

Instance variables

var name : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingControlTotalRecord:
    """An exact expected or observed wire total, with its declared type and unit."""

    name: str
    value: str
    value_type: Literal["integer", "decimal"]
    unit: str | None = None
    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if (
            any(type(value) is not str for value in (self.name, self.value, self.value_type))
            or (self.unit is not None and type(self.unit) is not str)
            or self.value_type not in {"integer", "decimal"}
        ):
            raise ValueError("control total evidence requires immutable typed values")
        reporting_identifier(self.name, maximum=128)
        if re.fullmatch(r"[A-Za-z][A-Za-z0-9_.:-]{0,127}", self.name) is None:
            raise ValueError("reporting total names require public metric identifiers")
        pattern = (
            r"-?(?:0|[1-9][0-9]*)"
            if self.value_type == "integer"
            else r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?"
        )
        if re.fullmatch(pattern, self.value) is None:
            raise ValueError("control totals require canonical numeric strings matching their type")
        if self.unit is not None:
            _public_text(self.unit, maximum=32)

    def to_wire(self) -> dict[str, str]:
        result = {"name": self.name, "value": self.value, "value_type": self.value_type}
        if self.unit is not None:
            result["unit"] = self.unit
        return result

    @classmethod
    def from_wire(cls, value: object) -> ReportingControlTotalRecord:
        if (
            not isinstance(value, dict)
            or not {"name", "value", "value_type"} <= set(value)
            or set(value) - {"name", "value", "value_type", "unit"}
        ):
            raise ValueError("invalid retained control total evidence")
        return cls(value["name"], value["value"], value["value_type"], value.get("unit"))
var unit : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingControlTotalRecord:
    """An exact expected or observed wire total, with its declared type and unit."""

    name: str
    value: str
    value_type: Literal["integer", "decimal"]
    unit: str | None = None
    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if (
            any(type(value) is not str for value in (self.name, self.value, self.value_type))
            or (self.unit is not None and type(self.unit) is not str)
            or self.value_type not in {"integer", "decimal"}
        ):
            raise ValueError("control total evidence requires immutable typed values")
        reporting_identifier(self.name, maximum=128)
        if re.fullmatch(r"[A-Za-z][A-Za-z0-9_.:-]{0,127}", self.name) is None:
            raise ValueError("reporting total names require public metric identifiers")
        pattern = (
            r"-?(?:0|[1-9][0-9]*)"
            if self.value_type == "integer"
            else r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?"
        )
        if re.fullmatch(pattern, self.value) is None:
            raise ValueError("control totals require canonical numeric strings matching their type")
        if self.unit is not None:
            _public_text(self.unit, maximum=32)

    def to_wire(self) -> dict[str, str]:
        result = {"name": self.name, "value": self.value, "value_type": self.value_type}
        if self.unit is not None:
            result["unit"] = self.unit
        return result

    @classmethod
    def from_wire(cls, value: object) -> ReportingControlTotalRecord:
        if (
            not isinstance(value, dict)
            or not {"name", "value", "value_type"} <= set(value)
            or set(value) - {"name", "value", "value_type", "unit"}
        ):
            raise ValueError("invalid retained control total evidence")
        return cls(value["name"], value["value"], value["value_type"], value.get("unit"))
var value : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingControlTotalRecord:
    """An exact expected or observed wire total, with its declared type and unit."""

    name: str
    value: str
    value_type: Literal["integer", "decimal"]
    unit: str | None = None
    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if (
            any(type(value) is not str for value in (self.name, self.value, self.value_type))
            or (self.unit is not None and type(self.unit) is not str)
            or self.value_type not in {"integer", "decimal"}
        ):
            raise ValueError("control total evidence requires immutable typed values")
        reporting_identifier(self.name, maximum=128)
        if re.fullmatch(r"[A-Za-z][A-Za-z0-9_.:-]{0,127}", self.name) is None:
            raise ValueError("reporting total names require public metric identifiers")
        pattern = (
            r"-?(?:0|[1-9][0-9]*)"
            if self.value_type == "integer"
            else r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?"
        )
        if re.fullmatch(pattern, self.value) is None:
            raise ValueError("control totals require canonical numeric strings matching their type")
        if self.unit is not None:
            _public_text(self.unit, maximum=32)

    def to_wire(self) -> dict[str, str]:
        result = {"name": self.name, "value": self.value, "value_type": self.value_type}
        if self.unit is not None:
            result["unit"] = self.unit
        return result

    @classmethod
    def from_wire(cls, value: object) -> ReportingControlTotalRecord:
        if (
            not isinstance(value, dict)
            or not {"name", "value", "value_type"} <= set(value)
            or set(value) - {"name", "value", "value_type", "unit"}
        ):
            raise ValueError("invalid retained control total evidence")
        return cls(value["name"], value["value"], value["value_type"], value.get("unit"))
var value_type : Literal['integer', 'decimal']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingControlTotalRecord:
    """An exact expected or observed wire total, with its declared type and unit."""

    name: str
    value: str
    value_type: Literal["integer", "decimal"]
    unit: str | None = None
    __pydantic_config__: ClassVar[ConfigDict] = ConfigDict(
        extra="forbid", hide_input_in_errors=True
    )

    def __post_init__(self) -> None:
        if (
            any(type(value) is not str for value in (self.name, self.value, self.value_type))
            or (self.unit is not None and type(self.unit) is not str)
            or self.value_type not in {"integer", "decimal"}
        ):
            raise ValueError("control total evidence requires immutable typed values")
        reporting_identifier(self.name, maximum=128)
        if re.fullmatch(r"[A-Za-z][A-Za-z0-9_.:-]{0,127}", self.name) is None:
            raise ValueError("reporting total names require public metric identifiers")
        pattern = (
            r"-?(?:0|[1-9][0-9]*)"
            if self.value_type == "integer"
            else r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?"
        )
        if re.fullmatch(pattern, self.value) is None:
            raise ValueError("control totals require canonical numeric strings matching their type")
        if self.unit is not None:
            _public_text(self.unit, maximum=32)

    def to_wire(self) -> dict[str, str]:
        result = {"name": self.name, "value": self.value, "value_type": self.value_type}
        if self.unit is not None:
            result["unit"] = self.unit
        return result

    @classmethod
    def from_wire(cls, value: object) -> ReportingControlTotalRecord:
        if (
            not isinstance(value, dict)
            or not {"name", "value", "value_type"} <= set(value)
            or set(value) - {"name", "value", "value_type", "unit"}
        ):
            raise ValueError("invalid retained control total evidence")
        return cls(value["name"], value["value"], value["value_type"], value.get("unit"))

Methods

def to_wire(self) ‑> dict[str, str]
Expand source code
def to_wire(self) -> dict[str, str]:
    result = {"name": self.name, "value": self.value, "value_type": self.value_type}
    if self.unit is not None:
        result["unit"] = self.unit
    return result
class ReportingCurrencyError (code: str, message: str)
Expand source code
class ReportingCurrencyError(ValueError):
    """Reporting money cannot be interpreted safely; ``code`` is stable."""

    def __init__(self, code: str, message: str) -> None:
        super().__init__(f"{code}: {message}")
        self.code = code

Reporting money cannot be interpreted safely; code is stable.

Ancestors

  • builtins.ValueError
  • builtins.Exception
  • builtins.BaseException
class ReportingDefinitionBinding (report_definition_uri: str,
report_definition_sha256: str,
schema_version: str,
schema_uri: str,
schema_sha256: str,
schema_dialect: str = 'https://json-schema.org/draft/2020-12/schema',
schema_ref_policy: str = 'local_fragment_only',
monetary_metric_units: tuple[tuple[str, str], ...] = (),
monetary_control_total_units: tuple[tuple[str, str], ...] = ())
Expand source code
@dataclass(frozen=True)
class ReportingDefinitionBinding:
    """The content-addressed report definition an obligation reports against.

    Core's wire records are self-describing: a retained revision names the
    exact definition and row schema it was produced under, by URI *and* digest,
    so a consumer reading it years later can verify it is reading what it
    thinks it is.  Without this, ``report_definition_id`` is just a string two
    parties hope still means the same thing.

    Optional on :class:`ReportingConfiguration` only so an in-process pilot can
    get moving; a seller advertising ``reporting.core`` must supply it, because
    :class:`~adcp.reporting.ledger.status.ReportingStatusHandler` cannot emit a
    schema-valid ``reporting-revision`` record without it.
    """

    report_definition_uri: str
    report_definition_sha256: str
    schema_version: str
    schema_uri: str
    schema_sha256: str
    schema_dialect: str = "https://json-schema.org/draft/2020-12/schema"
    schema_ref_policy: str = "local_fragment_only"
    # Trusted projections of the content-addressed definition, not adapter or
    # buyer context. Tuples keep these declarations immutable after acceptance.
    monetary_metric_units: tuple[tuple[str, str], ...] = ()
    monetary_control_total_units: tuple[tuple[str, str], ...] = ()

    def __post_init__(self) -> None:
        for field_name in ("monetary_metric_units", "monetary_control_total_units"):
            units = tuple(
                (name, validate_currency(unit)) for name, unit in getattr(self, field_name)
            )
            if len(dict(units)) != len(units):
                raise ValueError(f"duplicate names in {field_name}")
            object.__setattr__(self, field_name, units)

    def to_storage(self) -> dict[str, Any]:
        """Retain monetary semantics without changing AdCP wire records or old hashes."""
        payload = self.to_wire()
        if self.monetary_metric_units:
            payload["monetary_metric_units"] = list(self.monetary_metric_units)
        if self.monetary_control_total_units:
            payload["monetary_control_total_units"] = list(self.monetary_control_total_units)
        return payload

    def to_wire(self) -> dict[str, Any]:
        return {
            "report_definition_uri": self.report_definition_uri,
            "report_definition_sha256": self.report_definition_sha256,
            "schema_version": self.schema_version,
            "schema_uri": self.schema_uri,
            "schema_sha256": self.schema_sha256,
            "schema_dialect": self.schema_dialect,
            "schema_ref_policy": self.schema_ref_policy,
        }

The content-addressed report definition an obligation reports against.

Core's wire records are self-describing: a retained revision names the exact definition and row schema it was produced under, by URI and digest, so a consumer reading it years later can verify it is reading what it thinks it is. Without this, report_definition_id is just a string two parties hope still means the same thing.

Optional on :class:ReportingConfiguration only so an in-process pilot can get moving; a seller advertising reporting.core must supply it, because :class:~adcp.reporting.ledger.status.ReportingStatusHandler cannot emit a schema-valid reporting-revision record without it.

Instance variables

var monetary_control_total_units : tuple[tuple[str, str], ...]
var monetary_metric_units : tuple[tuple[str, str], ...]
var report_definition_sha256 : str
var report_definition_uri : str
var schema_dialect : str
var schema_ref_policy : str
var schema_sha256 : str
var schema_uri : str
var schema_version : str

Methods

def to_storage(self) ‑> dict[str, typing.Any]
Expand source code
def to_storage(self) -> dict[str, Any]:
    """Retain monetary semantics without changing AdCP wire records or old hashes."""
    payload = self.to_wire()
    if self.monetary_metric_units:
        payload["monetary_metric_units"] = list(self.monetary_metric_units)
    if self.monetary_control_total_units:
        payload["monetary_control_total_units"] = list(self.monetary_control_total_units)
    return payload

Retain monetary semantics without changing AdCP wire records or old hashes.

def to_wire(self) ‑> dict[str, typing.Any]
Expand source code
def to_wire(self) -> dict[str, Any]:
    return {
        "report_definition_uri": self.report_definition_uri,
        "report_definition_sha256": self.report_definition_sha256,
        "schema_version": self.schema_version,
        "schema_uri": self.schema_uri,
        "schema_sha256": self.schema_sha256,
        "schema_dialect": self.schema_dialect,
        "schema_ref_policy": self.schema_ref_policy,
    }
class ReportingDeliveryEscalation (consumer_mismatch_escalation: timedelta | None = None,
operations_contact_url: str | None = None,
operations_contact_email: str | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingDeliveryEscalation:
    """The seller's advertised escalation commitment for consumer mismatches.

    Both halves of AdCP 3.2.0-rc.3's
    ``reporting_delivery_capabilities.consumer_mismatch_escalation_seconds`` /
    ``operations_contact`` pair.  The schema makes the contact mandatory when
    the window is advertised, so this class refuses the window without one:
    committing to escalate with nowhere to escalate *to* is the failure the
    requirement exists to prevent.

    ``operations_contact`` is inert human-facing metadata.  Agents surface it
    to an operator and MUST NOT fetch the URL, send protocol traffic to it, or
    treat either value as a credential or a callback.
    """

    consumer_mismatch_escalation: timedelta | None = None
    operations_contact_url: str | None = None
    operations_contact_email: str | None = None

    def __post_init__(self) -> None:
        has_contact = bool(self.operations_contact_url or self.operations_contact_email)
        if self.consumer_mismatch_escalation is not None and not has_contact:
            raise ValueError(
                "consumer_mismatch_escalation requires operations_contact_url or "
                "operations_contact_email; the schema makes the contact mandatory so the "
                "escalation has a destination"
            )
        if (
            self.consumer_mismatch_escalation is not None
            and self.consumer_mismatch_escalation.total_seconds() < 0
        ):
            raise ValueError("consumer_mismatch_escalation cannot be negative")
        if (
            self.consumer_mismatch_escalation is not None
            and self.consumer_mismatch_escalation.total_seconds() % 1
        ):
            raise ValueError("consumer_mismatch_escalation requires integral seconds")
        if self.operations_contact_url is not None and not self.operations_contact_url.startswith(
            "https://"
        ):
            # Same hardened public-origin shape as the offering document URIs.
            # Enforcing the scheme here keeps "never dereference" true by
            # construction for the obvious loopback/credentialed cases.
            raise ValueError("operations_contact_url must be an https:// URL")

    def to_wire(self) -> dict[str, Any]:
        """The fragment a seller merges into its advertised capability block."""
        payload: dict[str, Any] = {}
        if self.consumer_mismatch_escalation is not None:
            payload["consumer_mismatch_escalation_seconds"] = int(
                self.consumer_mismatch_escalation.total_seconds()
            )
        contact = {
            key: value
            for key, value in (
                ("url", self.operations_contact_url),
                ("email", self.operations_contact_email),
            )
            if value is not None
        }
        if contact:
            payload["operations_contact"] = contact
        return payload

The seller's advertised escalation commitment for consumer mismatches.

Both halves of AdCP 3.2.0-rc.3's reporting_delivery_capabilities.consumer_mismatch_escalation_seconds / operations_contact pair. The schema makes the contact mandatory when the window is advertised, so this class refuses the window without one: committing to escalate with nowhere to escalate to is the failure the requirement exists to prevent.

operations_contact is inert human-facing metadata. Agents surface it to an operator and MUST NOT fetch the URL, send protocol traffic to it, or treat either value as a credential or a callback.

Instance variables

var consumer_mismatch_escalation : datetime.timedelta | None
var operations_contact_email : str | None
var operations_contact_url : str | None

Methods

def to_wire(self) ‑> dict[str, typing.Any]
Expand source code
def to_wire(self) -> dict[str, Any]:
    """The fragment a seller merges into its advertised capability block."""
    payload: dict[str, Any] = {}
    if self.consumer_mismatch_escalation is not None:
        payload["consumer_mismatch_escalation_seconds"] = int(
            self.consumer_mismatch_escalation.total_seconds()
        )
    contact = {
        key: value
        for key, value in (
            ("url", self.operations_contact_url),
            ("email", self.operations_contact_email),
        )
        if value is not None
    }
    if contact:
        payload["operations_contact"] = contact
    return payload

The fragment a seller merges into its advertised capability block.

class ReportingDeliveryPrincipal (account_id: str, consumer_id: str)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryPrincipal(_ClosedValue):
    """Trusted account and consumer identity, resolved from authenticated transport."""

    account_id: str
    consumer_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        principal_reference(self.account_id)
        from adcp.reporting.evidence import consumer_reference

        consumer_reference(self.consumer_id)

Trusted account and consumer identity, resolved from authenticated transport.

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var account_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryPrincipal(_ClosedValue):
    """Trusted account and consumer identity, resolved from authenticated transport."""

    account_id: str
    consumer_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        principal_reference(self.account_id)
        from adcp.reporting.evidence import consumer_reference

        consumer_reference(self.consumer_id)
var consumer_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryPrincipal(_ClosedValue):
    """Trusted account and consumer identity, resolved from authenticated transport."""

    account_id: str
    consumer_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        principal_reference(self.account_id)
        from adcp.reporting.evidence import consumer_reference

        consumer_reference(self.consumer_id)
class ReportingDeliveryScope (generation_key: ReportingConfigurationGenerationKey,
consumer_id: str,
reporting_obligation_id: str)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryScope(_ClosedValue):
    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    reporting_obligation_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        if type(self.generation_key) is not ReportingConfigurationGenerationKey:
            raise ValueError("reporting scope requires the typed configuration generation")
        self.principal
        if self.consumer_id != self.generation_key.consumer_id:
            raise ValueError("reporting generation belongs to another caller")
        reporting_identifier(self.generation_key.delivery_config_id, maximum=64)
        _positive(self.generation_key.delivery_config_version)
        reporting_identifier(self.reporting_obligation_id, maximum=255)

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)

ReportingDeliveryScope(generation_key: 'ReportingConfigurationGenerationKey', consumer_id: 'str', reporting_obligation_id: 'str')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var consumer_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryScope(_ClosedValue):
    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    reporting_obligation_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        if type(self.generation_key) is not ReportingConfigurationGenerationKey:
            raise ValueError("reporting scope requires the typed configuration generation")
        self.principal
        if self.consumer_id != self.generation_key.consumer_id:
            raise ValueError("reporting generation belongs to another caller")
        reporting_identifier(self.generation_key.delivery_config_id, maximum=64)
        _positive(self.generation_key.delivery_config_version)
        reporting_identifier(self.reporting_obligation_id, maximum=255)

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var generation_key : ReportingConfigurationGenerationKey
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryScope(_ClosedValue):
    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    reporting_obligation_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        if type(self.generation_key) is not ReportingConfigurationGenerationKey:
            raise ValueError("reporting scope requires the typed configuration generation")
        self.principal
        if self.consumer_id != self.generation_key.consumer_id:
            raise ValueError("reporting generation belongs to another caller")
        reporting_identifier(self.generation_key.delivery_config_id, maximum=64)
        _positive(self.generation_key.delivery_config_version)
        reporting_identifier(self.reporting_obligation_id, maximum=255)

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
prop principal : ReportingDeliveryPrincipal
Expand source code
@property
def principal(self) -> ReportingDeliveryPrincipal:
    return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var reporting_obligation_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDeliveryScope(_ClosedValue):
    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    reporting_obligation_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        if type(self.generation_key) is not ReportingConfigurationGenerationKey:
            raise ValueError("reporting scope requires the typed configuration generation")
        self.principal
        if self.consumer_id != self.generation_key.consumer_id:
            raise ValueError("reporting generation belongs to another caller")
        reporting_identifier(self.generation_key.delivery_config_id, maximum=64)
        _positive(self.generation_key.delivery_config_version)
        reporting_identifier(self.reporting_obligation_id, maximum=255)

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
class ReportingDestinationBinding (generation_key: ReportingConfigurationGenerationKey,
consumer_id: str,
destination_ref: str,
trusted_binding_ref: str,
method: DeliveryMethod,
transport: str,
verification_profile: VerificationProfile,
reconciliation_mode: "Literal['delivery_only', 'consumer_receipt']",
feed_purpose: "Literal['pacing', 'analytics', 'billing']",
resource_retention_days: int,
created_at: datetime,
format: ReportingFormat | None = None,
reader_compatibility: tuple[str, ...] = (),
success_status: "Literal['available', 'delivered']" = 'available',
*,
kind: "Literal['destination_binding']" = 'destination_binding')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)

Immutable public portion of one trusted principal/configuration binding.

trusted_binding_ref identifies an immutable trusted configuration, not a mutable 'latest' alias. Credentials are resolved later behind that reference. Storing this record neither configures a writer nor advertises a capability.

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var consumer_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var created_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var destination_ref : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var feed_purpose : Literal['pacing', 'analytics', 'billing']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var format : Literal['jsonl', 'csv', 'parquet', 'avro', 'orc'] | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var generation_key : ReportingConfigurationGenerationKey
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var kind : Literal['destination_binding']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var method : Literal['file_transfer', 'dataset_share', 'warehouse_materialization']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
prop principal : ReportingDeliveryPrincipal
Expand source code
@property
def principal(self) -> ReportingDeliveryPrincipal:
    return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var reader_compatibility : tuple[str, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var reconciliation_mode : Literal['delivery_only', 'consumer_receipt']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var resource_retention_days : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var success_status : Literal['available', 'delivered']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var transport : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var trusted_binding_ref : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
var verification_profile : Literal['canonical_digest', 'manifest_checksums', 'native_commit']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingDestinationBinding(_ClosedValue):
    """Immutable public portion of one trusted principal/configuration binding.

    ``trusted_binding_ref`` identifies an immutable trusted configuration, not a
    mutable 'latest' alias. Credentials are resolved later behind that reference.
    Storing this record neither configures a writer nor advertises a capability.
    """

    generation_key: ReportingConfigurationGenerationKey
    consumer_id: str
    destination_ref: str
    trusted_binding_ref: str = field(repr=False)
    method: DeliveryMethod
    transport: str
    verification_profile: VerificationProfile
    reconciliation_mode: Literal["delivery_only", "consumer_receipt"]
    feed_purpose: Literal["pacing", "analytics", "billing"]
    resource_retention_days: int
    created_at: datetime
    format: ReportingFormat | None = None
    reader_compatibility: tuple[str, ...] = ()
    success_status: Literal["available", "delivered"] = "available"
    kind: Literal["destination_binding"] = field(default="destination_binding", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        ReportingDeliveryScope(self.generation_key, self.consumer_id, "binding")
        destination_reference(self.destination_ref)
        destination_reference(self.trusted_binding_ref)
        if re.fullmatch(r"[a-z][a-z0-9_.-]{0,63}", self.transport) is None:
            raise ValueError("reporting transport requires a public protocol label")
        reporting_identifier(self.transport, maximum=64)
        _positive(self.resource_retention_days)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
        if self.method == "file_transfer" and self.format is None:
            raise ValueError("file transfer requires a declared format")
        if self.verification_profile == "manifest_checksums" and self.method != "file_transfer":
            raise ValueError("manifest checksum verification requires file transfer")
        if self.method == "warehouse_materialization" and self.success_status != "delivered":
            raise ValueError("warehouse materialization requires destination delivery")
        if self.method == "dataset_share" and self.success_status != "available":
            raise ValueError("dataset share requires representative-consumer availability")
        if self.feed_purpose == "billing" and self.verification_profile != "canonical_digest":
            raise ValueError("billing receipts require canonical digest verification")

    @property
    def principal(self) -> ReportingDeliveryPrincipal:
        return ReportingDeliveryPrincipal(self.generation_key.account_id, self.consumer_id)
class ReportingDestinationStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingDestinationStore(Protocol):
    """Trusted configuration ingestion. Never accept a destination from a receipt body."""

    async def put_destination_binding(
        self, record: ReportingDestinationBinding
    ) -> tuple[ReportingDestinationBinding, bool]: ...

    async def bind_obligation_delivery(
        self, record: ReportingObligationDeliveryRecord
    ) -> tuple[ReportingObligationDeliveryRecord, bool]: ...

    async def get_destination_binding(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        generation_key: ReportingConfigurationGenerationKey,
    ) -> ReportingDestinationBinding | None: ...

    async def get_obligation_delivery(
        self, scope: ReportingDeliveryScope
    ) -> ReportingObligationDeliveryRecord | None: ...

Trusted configuration ingestion. Never accept a destination from a receipt body.

Ancestors

  • typing.Protocol
  • typing.Generic

Subclasses

Methods

async def bind_obligation_delivery(self,
record: ReportingObligationDeliveryRecord) ‑> tuple[ReportingObligationDeliveryRecord, bool]
Expand source code
async def bind_obligation_delivery(
    self, record: ReportingObligationDeliveryRecord
) -> tuple[ReportingObligationDeliveryRecord, bool]: ...
async def get_destination_binding(self,
*,
caller: ReportingDeliveryPrincipal,
generation_key: ReportingConfigurationGenerationKey) ‑> ReportingDestinationBinding | None
Expand source code
async def get_destination_binding(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    generation_key: ReportingConfigurationGenerationKey,
) -> ReportingDestinationBinding | None: ...
async def get_obligation_delivery(self,
scope: ReportingDeliveryScope) ‑> ReportingObligationDeliveryRecord | None
Expand source code
async def get_obligation_delivery(
    self, scope: ReportingDeliveryScope
) -> ReportingObligationDeliveryRecord | None: ...
async def put_destination_binding(self,
record: ReportingDestinationBinding) ‑> tuple[ReportingDestinationBinding, bool]
Expand source code
async def put_destination_binding(
    self, record: ReportingDestinationBinding
) -> tuple[ReportingDestinationBinding, bool]: ...
class ReportingIssue (issue_id: str,
code: str,
severity: "Literal['info', 'delayed', 'action_required']",
responsible_party: "Literal['buyer', 'seller', 'provider']",
recommended_action: str,
reporting_obligation_id: str | None = None,
delivery_config_id: str | None = None,
delivery_config_version: int | None = None,
feed_purpose: str | None = None,
media_buy_ids: tuple[str, ...] = (),
period_start: datetime | None = None,
period_end: datetime | None = None,
expected_at: datetime | None = None,
reporting_status_id: str | None = None,
message: str | None = None,
opened_at: datetime | None = None,
issue_state: ReportingIssueStateValue | None = None,
external_ref: str | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingIssue:
    """A typed, actionable statement about why a scope is not healthy.

    Issue identity is stable and derived, not stored: the same unresolved
    condition yields the same ``issue_id`` on every read, so a consumer can
    deduplicate across polls and a notification can reference it.
    """

    issue_id: str
    code: str
    severity: Literal["info", "delayed", "action_required"]
    responsible_party: Literal["buyer", "seller", "provider"]
    recommended_action: str
    reporting_obligation_id: str | None = None
    delivery_config_id: str | None = None
    delivery_config_version: int | None = None
    feed_purpose: str | None = None
    media_buy_ids: tuple[str, ...] = ()
    period_start: datetime | None = None
    period_end: datetime | None = None
    expected_at: datetime | None = None
    reporting_status_id: str | None = None
    message: str | None = None
    # AdCP 3.2.0-rc.3 issue lifecycle. ``opened_at`` is when the seller first
    # observed this logical condition and MUST NOT advance while the same
    # ``issue_id`` is re-emitted -- it anchors the escalation clock, so a
    # re-emission that reset it would let a seller hold an unresolved mismatch
    # below action_required forever. Required on CONSUMER_STATUS_MISMATCH.
    opened_at: datetime | None = None
    issue_state: ReportingIssueStateValue | None = None
    # Inert correlation text for the party's own tracker. Never dereferenced,
    # resolved, or executed, and never reused across callers on a
    # caller-scoped issue -- that would leak one tenant's blast radius to
    # another.
    external_ref: str | None = None

    def to_wire(self) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "issue_id": self.issue_id,
            "code": self.code,
            "severity": self.severity,
            "responsible_party": self.responsible_party,
            "recommended_action": self.recommended_action,
        }
        optional: dict[str, Any] = {
            "reporting_obligation_id": self.reporting_obligation_id,
            "delivery_config_id": self.delivery_config_id,
            "delivery_config_version": self.delivery_config_version,
            "feed_purpose": self.feed_purpose,
            "period_start": self.period_start,
            "period_end": self.period_end,
            "expected_at": self.expected_at,
            "reporting_status_id": self.reporting_status_id,
            "message": self.message,
            "opened_at": self.opened_at,
            "issue_state": self.issue_state,
            "external_ref": self.external_ref,
        }
        for key, value in optional.items():
            if value is not None:
                payload[key] = value.isoformat() if isinstance(value, datetime) else value
        if self.media_buy_ids:
            payload["media_buy_ids"] = list(self.media_buy_ids)
        return payload

A typed, actionable statement about why a scope is not healthy.

Issue identity is stable and derived, not stored: the same unresolved condition yields the same issue_id on every read, so a consumer can deduplicate across polls and a notification can reference it.

Instance variables

var code : str
var delivery_config_id : str | None
var delivery_config_version : int | None
var expected_at : datetime.datetime | None
var external_ref : str | None
var feed_purpose : str | None
var issue_id : str
var issue_state : Literal['open', 'acknowledged', 'resolved', 'waived'] | None
var media_buy_ids : tuple[str, ...]
var message : str | None
var opened_at : datetime.datetime | None
var period_end : datetime.datetime | None
var period_start : datetime.datetime | None
var recommended_action : str
var reporting_obligation_id : str | None
var reporting_status_id : str | None
var responsible_party : Literal['buyer', 'seller', 'provider']
var severity : Literal['info', 'delayed', 'action_required']

Methods

def to_wire(self) ‑> dict[str, typing.Any]
Expand source code
def to_wire(self) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "issue_id": self.issue_id,
        "code": self.code,
        "severity": self.severity,
        "responsible_party": self.responsible_party,
        "recommended_action": self.recommended_action,
    }
    optional: dict[str, Any] = {
        "reporting_obligation_id": self.reporting_obligation_id,
        "delivery_config_id": self.delivery_config_id,
        "delivery_config_version": self.delivery_config_version,
        "feed_purpose": self.feed_purpose,
        "period_start": self.period_start,
        "period_end": self.period_end,
        "expected_at": self.expected_at,
        "reporting_status_id": self.reporting_status_id,
        "message": self.message,
        "opened_at": self.opened_at,
        "issue_state": self.issue_state,
        "external_ref": self.external_ref,
    }
    for key, value in optional.items():
        if value is not None:
            payload[key] = value.isoformat() if isinstance(value, datetime) else value
    if self.media_buy_ids:
        payload["media_buy_ids"] = list(self.media_buy_ids)
    return payload
class ReportingIssueLifecycle (issue_key: str,
issue_id: str,
account_id: str,
opened_at: datetime,
issue_state: ReportingIssueStateValue = 'open',
generation: int = 1,
consumer_id: str | None = None,
external_ref: str | None = None,
retired_at: datetime | None = None,
waived_reporting_status_id: str | None = None,
waived_conflict_sha256: str | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingIssueLifecycle:
    """Durable state for one logical issue, so ``opened_at`` can stay fixed.

    Issue *identity* elsewhere in this module is derived (see
    :func:`~adcp.reporting.ledger.health.issue_id_for`) because the conditions
    it names are monotone for one immutable obligation.  A consumer mismatch is
    not monotone: the buyer can supersede its statement, the seller can restate,
    and the same logical disagreement can go quiet and come back.  AdCP
    3.2.0-rc.3 also requires ``opened_at`` to survive every re-emission,
    because it anchors the escalation clock -- a derived timestamp would reset
    on each poll and an unattended mismatch would never escalate.

    ``issue_key`` identifies the *condition*; ``issue_id`` identifies one
    *occurrence* of it. Resolution bumps ``generation`` on recurrence; an
    exact bilateral waiver preserves its terminal row and a later disagreement
    uses a new private condition key. Both receive a new ``issue_id`` and
    ``opened_at`` as the spec requires, while an unresolved condition keeps both
    across a severity change from ``delayed`` to ``action_required``.
    """

    issue_key: str
    issue_id: str
    account_id: str
    opened_at: datetime
    issue_state: ReportingIssueStateValue = "open"
    generation: int = 1
    #: Caller-scoped issues (every consumer mismatch) carry the consumer whose
    #: statement caused them. ``None`` is a seller-wide condition.
    consumer_id: str | None = None
    external_ref: str | None = None
    retired_at: datetime | None = None
    #: Private rc.6 waiver binding, captured under the source transaction.
    #: A legacy waiver without this evidence cannot suppress a new projection.
    waived_reporting_status_id: str | None = None
    waived_conflict_sha256: str | None = None

    def __post_init__(self) -> None:
        principal_reference(self.account_id)
        if self.consumer_id is not None:
            consumer_reference(self.consumer_id)

    @property
    def live(self) -> bool:
        """Whether this occurrence still stands, publishable or not.

        ``waived`` retains its terminal record and suppresses only its exact
        statement/conflict binding. A later disagreement uses a new private
        condition key, leaving this audit record and the one-live-row constraint
        intact. Agreement never rewrites a waiver as ``resolved``.

        ``resolved`` frees the condition to recur under a new
        ``issue_id``, and only the projection can set it (see
        :meth:`~adcp.reporting.ledger.store.ReportingLedgerStore.retire_issue`).
        """
        return self.issue_state in {"open", "acknowledged", "waived"}

    @property
    def published(self) -> bool:
        """Whether this occurrence belongs in ``issues[]``.

        Only ``open`` and ``acknowledged``.  Retiring an issue removes it from
        the projection rather than publishing it in a terminal state, so a
        reader that treats a nonempty ``issues[]`` as degradation stays
        correct. Waived occurrences contribute neither an issue nor degradation.
        """
        return self.issue_state in {"open", "acknowledged"}

Durable state for one logical issue, so opened_at can stay fixed.

Issue identity elsewhere in this module is derived (see :func:~adcp.reporting.ledger.health.issue_id_for) because the conditions it names are monotone for one immutable obligation. A consumer mismatch is not monotone: the buyer can supersede its statement, the seller can restate, and the same logical disagreement can go quiet and come back. AdCP 3.2.0-rc.3 also requires opened_at to survive every re-emission, because it anchors the escalation clock – a derived timestamp would reset on each poll and an unattended mismatch would never escalate.

issue_key identifies the condition; issue_id identifies one occurrence of it. Resolution bumps generation on recurrence; an exact bilateral waiver preserves its terminal row and a later disagreement uses a new private condition key. Both receive a new issue_id and opened_at as the spec requires, while an unresolved condition keeps both across a severity change from delayed to action_required.

Instance variables

var account_id : str
var consumer_id : str | None

Caller-scoped issues (every consumer mismatch) carry the consumer whose statement caused them. None is a seller-wide condition.

var external_ref : str | None
var generation : int
var issue_id : str
var issue_key : str
var issue_state : Literal['open', 'acknowledged', 'resolved', 'waived']
prop live : bool
Expand source code
@property
def live(self) -> bool:
    """Whether this occurrence still stands, publishable or not.

    ``waived`` retains its terminal record and suppresses only its exact
    statement/conflict binding. A later disagreement uses a new private
    condition key, leaving this audit record and the one-live-row constraint
    intact. Agreement never rewrites a waiver as ``resolved``.

    ``resolved`` frees the condition to recur under a new
    ``issue_id``, and only the projection can set it (see
    :meth:`~adcp.reporting.ledger.store.ReportingLedgerStore.retire_issue`).
    """
    return self.issue_state in {"open", "acknowledged", "waived"}

Whether this occurrence still stands, publishable or not.

waived retains its terminal record and suppresses only its exact statement/conflict binding. A later disagreement uses a new private condition key, leaving this audit record and the one-live-row constraint intact. Agreement never rewrites a waiver as resolved.

resolved frees the condition to recur under a new issue_id, and only the projection can set it (see :meth:~adcp.reporting.ledger.store.ReportingLedgerStore.retire_issue).

var opened_at : datetime.datetime
prop published : bool
Expand source code
@property
def published(self) -> bool:
    """Whether this occurrence belongs in ``issues[]``.

    Only ``open`` and ``acknowledged``.  Retiring an issue removes it from
    the projection rather than publishing it in a terminal state, so a
    reader that treats a nonempty ``issues[]`` as degradation stays
    correct. Waived occurrences contribute neither an issue nor degradation.
    """
    return self.issue_state in {"open", "acknowledged"}

Whether this occurrence belongs in issues[].

Only open and acknowledged. Retiring an issue removes it from the projection rather than publishing it in a terminal state, so a reader that treats a nonempty issues[] as degradation stays correct. Waived occurrences contribute neither an issue nor degradation.

var retired_at : datetime.datetime | None
var waived_conflict_sha256 : str | None
var waived_reporting_status_id : str | None

Private rc.6 waiver binding, captured under the source transaction. A legacy waiver without this evidence cannot suppress a new projection.

class ReportingLedgerStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingLedgerStore(Protocol):
    """Durable home for obligations, revisions, adjustments, and statuses."""

    async def create_schema(self) -> None:
        """Idempotently create or upgrade this store's schema. Safe on every boot."""
        ...

    # -- configurations --------------------------------------------------

    async def put_configuration(self, configuration: ReportingConfiguration) -> None:
        """Record an accepted configuration generation.

        A generation is immutable: re-putting one with changed content is a
        conflict, because a buyer that retained it derives expectations from it.
        """
        ...

    async def list_configurations(
        self, *, caller: ReportingCaller, delivery_config_ids: Sequence[str] | None = None
    ) -> tuple[ReportingConfiguration, ...]: ...

    async def list_all_configurations(self) -> tuple[ReportingConfiguration, ...]:
        """Enumerate retained generations for trusted service startup recovery.

        This administrative API is never exposed through buyer task handlers;
        those continue to use account-scoped reads after authorization.
        """
        raise NotImplementedError

    # -- obligations -----------------------------------------------------

    async def commit_obligation(
        self, obligation: ReportingObligationRecord
    ) -> ReportingObligationRecord:
        """Commit an obligation, or return the existing one for its period.

        Idempotent by ``(account, config generation, period)``, not by id: two
        workers racing a period close must converge on one obligation.
        New records require an explicit, validated currency. A legacy record
        with unknown currency can be read/replayed but cannot be filled here.
        """
        ...

    async def get_obligation(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> ReportingObligationRecord | None: ...

    async def find_obligation(
        self,
        *,
        account_id: str,
        consumer_id: str,
        delivery_config_id: str,
        delivery_config_version: int,
        period_start: datetime,
        period_end: datetime,
    ) -> ReportingObligationRecord | None: ...

    # -- revisions -------------------------------------------------------

    async def commit_revision(
        self,
        revision: ReportingRevisionRecord,
        rows: Sequence[dict[str, Any]],
    ) -> ReportingRevisionRecord:
        """Commit an immutable revision and its frozen rows.

        Enforces terminal officials, exact supersession, and idempotent replay
        of an identical ``reporting_revision_id``.
        """
        ...

    async def list_revisions(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> tuple[ReportingRevisionRecord, ...]: ...

    async def get_revision(
        self, *, account_id: str, reporting_revision_id: str
    ) -> ReportingRevisionRecord | None: ...

    async def read_revision_rows(
        self,
        *,
        account_id: str,
        reporting_revision_id: str,
        cursor: str | None = None,
        limit: int = 500,
    ) -> ReportingRowPage:
        """Walk one revision's frozen rows in stable order."""
        ...

    async def set_revision_readable(
        self, *, account_id: str, reporting_revision_id: str, readable: bool
    ) -> None:
        """Record that a revision's content is (no longer) readable.

        Retention expiry and storage loss are real; representing them is how an
        obligation becomes honestly ``action_required`` instead of staying
        ``complete`` over evidence nobody can read.
        """
        ...

    # -- adjustments -----------------------------------------------------

    async def commit_adjustment(
        self, adjustment: ReportingAdjustmentRecord
    ) -> ReportingAdjustmentRecord: ...

    async def list_adjustments(
        self, *, account_id: str, reporting_revision_ids: Sequence[str]
    ) -> tuple[ReportingAdjustmentRecord, ...]: ...

    # -- consumer status (preview) ---------------------------------------

    async def record_consumer_status(
        self, status: ConsumerStatusRecord
    ) -> tuple[ConsumerStatusRecord, bool]:
        """Append a consumer status statement, superseding the chain's leaf.

        Returns ``(record, recorded)`` where ``recorded`` is ``False`` for an
        exact idempotent replay.  Supersession is atomic: naming a stale or
        missing leaf must raise rather than fork the chain, so a successful
        retry cannot erase a recorded outage.
        """
        ...

    async def resolve_consumer_status_replay(
        self, status: ConsumerStatusRecord
    ) -> ConsumerStatusRecord | None:
        """Return the stored row when this exact statement was already recorded.

        Exists so the ingest can answer "is this an exact retry?" *before* it
        validates anything time-dependent. ``record_consumer_status`` already
        makes that check, but it runs last, and some validation the ingest does
        first -- notably "``content_mismatch`` must name the revision the seller
        currently requires" -- has an answer that changes as the seller
        publishes. An exact retry sent after a restatement would then be
        rejected for a reason that did not apply when it was first accepted,
        and the spec's ``result_mapping`` requires ``unchanged``.

        Returns ``None`` when the id is unknown, so the caller proceeds to
        validate and record. Raises :class:`LedgerConflictError` with
        ``STATUS_IDENTITY_CONFLICT`` when the id exists with different content:
        reuse with changed content is still a conflict, and answering
        ``unchanged`` there would let a buyer rewrite a recorded statement.
        """
        ...

    async def list_consumer_statuses(
        self,
        *,
        account_id: str,
        consumer_id: str,
        reporting_obligation_ids: Sequence[str] | None = None,
    ) -> tuple[ConsumerStatusRecord, ...]: ...

    # -- issue lifecycle -------------------------------------------------

    async def ensure_issue_opened(
        self,
        *,
        issue_key: str,
        account_id: str,
        consumer_id: str | None,
        observed_at: datetime,
    ) -> ReportingIssueLifecycle:
        """Return this condition's live occurrence, opening one if needed.

        Idempotent: the second caller gets the first caller's ``opened_at``.
        That is the whole point -- AdCP 3.2.0-rc.3 anchors the escalation clock
        to ``opened_at``, so a re-emission that advanced it would let an
        unattended mismatch stay below ``action_required`` indefinitely.

        This is the one write the read path performs. ``get_reporting_status``
        is where a consumer mismatch is *first observed*, and a stable
        first-observation timestamp cannot be derived from immutable evidence
        alone. Implementations must make it safe under concurrent reads; two
        readers of one condition must converge on one row rather than open two
        occurrences.

        A condition retired as ``resolved`` or ``waived`` opens a *new*
        occurrence with the next ``generation``, a new ``issue_id``, and a new
        ``opened_at``, which is exactly the spec's "a recurrence after
        retirement receives a new issue_id".
        """
        ...

    async def set_issue_state(
        self,
        *,
        issue_key: str,
        account_id: str,
        state: Literal["acknowledged", "waived"],
        at: datetime,
        external_ref: str | None = None,
    ) -> ReportingIssueLifecycle:
        """Move a live occurrence forward, or attach an ``external_ref``.

        Deliberately cannot set ``resolved``. The spec forbids retiring a
        ``CONSUMER_STATUS_MISMATCH`` out of a degraded projection while the
        statement that caused it is still the consumer's current leaf, so
        resolution is not an operator action -- it is what the projection does
        when the condition actually clears (see :meth:`retire_issue`).
        Otherwise a seller could unilaterally erase a buyer-attributed
        disagreement, which is the one outcome this separately attributed loop
        exists to prevent.

        ``waived`` requires explicit off-protocol agreement by this consumer
        and seller for the exact issue, causing statement and diagnosed
        conflict. The adopter must retain its private consent audit before
        calling this method; ``external_ref`` is inert correlation, not proof.
        Rc.6 removes that issue and restores underlying seller health, without
        changing the consumer statement. Later statements or different
        conflicts are evaluated independently, never covered by this waiver.
        """
        ...

    async def retire_issue(
        self, *, issue_key: str, account_id: str, at: datetime
    ) -> ReportingIssueLifecycle | None:
        """Mark this condition's live occurrence ``resolved``.

        Called by the projection when the condition no longer holds. Returns
        ``None`` when there was nothing live to retire, so a repeated
        projection is convergent rather than an error.
        """
        ...

    async def get_issue(self, *, issue_key: str, account_id: str) -> ReportingIssueLifecycle | None:
        """The live occurrence of this condition, or ``None``."""
        ...

    # -- snapshots and pagination ----------------------------------------

    async def open_snapshot(
        self, *, caller: ReportingCaller, filters_fingerprint: str
    ) -> LedgerSnapshot:
        """Take a consistent read boundary for one account and filter set."""
        ...

    async def read_page(
        self,
        *,
        snapshot: LedgerSnapshot,
        consumer_id: str | None,
        delivery_config_ids: Sequence[str] | None,
        media_buy_ids: Sequence[str] | None,
        offset: int,
        limit: int,
        changes_after_sequence: int | None,
    ) -> LedgerPage: ...

    # -- worker leasing --------------------------------------------------

    async def lease_period_close(
        self, *, worker_id: str, now: datetime, lease_seconds: float
    ) -> LeasedConfiguration | None:
        """Lease one configuration generation for period-close work.

        Leasing rather than locking so a worker that dies mid-close releases its
        work by expiry instead of wedging the period forever, and so two workers
        cannot both close the same period.  Implementations should prefer the
        generation whose work is most overdue.
        """
        ...

    async def release_period_close(self, lease: LeasedConfiguration, *, worker_id: str) -> None: ...

Durable home for obligations, revisions, adjustments, and statuses.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def commit_adjustment(self,
adjustment: ReportingAdjustmentRecord) ‑> ReportingAdjustmentRecord
Expand source code
async def commit_adjustment(
    self, adjustment: ReportingAdjustmentRecord
) -> ReportingAdjustmentRecord: ...
async def commit_obligation(self,
obligation: ReportingObligationRecord) ‑> ReportingObligationRecord
Expand source code
async def commit_obligation(
    self, obligation: ReportingObligationRecord
) -> ReportingObligationRecord:
    """Commit an obligation, or return the existing one for its period.

    Idempotent by ``(account, config generation, period)``, not by id: two
    workers racing a period close must converge on one obligation.
    New records require an explicit, validated currency. A legacy record
    with unknown currency can be read/replayed but cannot be filled here.
    """
    ...

Commit an obligation, or return the existing one for its period.

Idempotent by (account, config generation, period), not by id: two workers racing a period close must converge on one obligation. New records require an explicit, validated currency. A legacy record with unknown currency can be read/replayed but cannot be filled here.

async def commit_revision(self,
revision: ReportingRevisionRecord,
rows: Sequence[dict[str, Any]]) ‑> ReportingRevisionRecord
Expand source code
async def commit_revision(
    self,
    revision: ReportingRevisionRecord,
    rows: Sequence[dict[str, Any]],
) -> ReportingRevisionRecord:
    """Commit an immutable revision and its frozen rows.

    Enforces terminal officials, exact supersession, and idempotent replay
    of an identical ``reporting_revision_id``.
    """
    ...

Commit an immutable revision and its frozen rows.

Enforces terminal officials, exact supersession, and idempotent replay of an identical reporting_revision_id.

async def create_schema(self) ‑> None
Expand source code
async def create_schema(self) -> None:
    """Idempotently create or upgrade this store's schema. Safe on every boot."""
    ...

Idempotently create or upgrade this store's schema. Safe on every boot.

async def ensure_issue_opened(self,
*,
issue_key: str,
account_id: str,
consumer_id: str | None,
observed_at: datetime) ‑> ReportingIssueLifecycle
Expand source code
async def ensure_issue_opened(
    self,
    *,
    issue_key: str,
    account_id: str,
    consumer_id: str | None,
    observed_at: datetime,
) -> ReportingIssueLifecycle:
    """Return this condition's live occurrence, opening one if needed.

    Idempotent: the second caller gets the first caller's ``opened_at``.
    That is the whole point -- AdCP 3.2.0-rc.3 anchors the escalation clock
    to ``opened_at``, so a re-emission that advanced it would let an
    unattended mismatch stay below ``action_required`` indefinitely.

    This is the one write the read path performs. ``get_reporting_status``
    is where a consumer mismatch is *first observed*, and a stable
    first-observation timestamp cannot be derived from immutable evidence
    alone. Implementations must make it safe under concurrent reads; two
    readers of one condition must converge on one row rather than open two
    occurrences.

    A condition retired as ``resolved`` or ``waived`` opens a *new*
    occurrence with the next ``generation``, a new ``issue_id``, and a new
    ``opened_at``, which is exactly the spec's "a recurrence after
    retirement receives a new issue_id".
    """
    ...

Return this condition's live occurrence, opening one if needed.

Idempotent: the second caller gets the first caller's opened_at. That is the whole point – AdCP 3.2.0-rc.3 anchors the escalation clock to opened_at, so a re-emission that advanced it would let an unattended mismatch stay below action_required indefinitely.

This is the one write the read path performs. get_reporting_status is where a consumer mismatch is first observed, and a stable first-observation timestamp cannot be derived from immutable evidence alone. Implementations must make it safe under concurrent reads; two readers of one condition must converge on one row rather than open two occurrences.

A condition retired as resolved or waived opens a new occurrence with the next generation, a new issue_id, and a new opened_at, which is exactly the spec's "a recurrence after retirement receives a new issue_id".

async def find_obligation(self,
*,
account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
period_start: datetime,
period_end: datetime) ‑> ReportingObligationRecord | None
Expand source code
async def find_obligation(
    self,
    *,
    account_id: str,
    consumer_id: str,
    delivery_config_id: str,
    delivery_config_version: int,
    period_start: datetime,
    period_end: datetime,
) -> ReportingObligationRecord | None: ...
async def get_issue(self, *, issue_key: str, account_id: str) ‑> ReportingIssueLifecycle | None
Expand source code
async def get_issue(self, *, issue_key: str, account_id: str) -> ReportingIssueLifecycle | None:
    """The live occurrence of this condition, or ``None``."""
    ...

The live occurrence of this condition, or None.

async def get_obligation(self, *, account_id: str, reporting_obligation_id: str) ‑> ReportingObligationRecord | None
Expand source code
async def get_obligation(
    self, *, account_id: str, reporting_obligation_id: str
) -> ReportingObligationRecord | None: ...
async def get_revision(self, *, account_id: str, reporting_revision_id: str) ‑> ReportingRevisionRecord | None
Expand source code
async def get_revision(
    self, *, account_id: str, reporting_revision_id: str
) -> ReportingRevisionRecord | None: ...
async def lease_period_close(self, *, worker_id: str, now: datetime, lease_seconds: float) ‑> LeasedConfiguration | None
Expand source code
async def lease_period_close(
    self, *, worker_id: str, now: datetime, lease_seconds: float
) -> LeasedConfiguration | None:
    """Lease one configuration generation for period-close work.

    Leasing rather than locking so a worker that dies mid-close releases its
    work by expiry instead of wedging the period forever, and so two workers
    cannot both close the same period.  Implementations should prefer the
    generation whose work is most overdue.
    """
    ...

Lease one configuration generation for period-close work.

Leasing rather than locking so a worker that dies mid-close releases its work by expiry instead of wedging the period forever, and so two workers cannot both close the same period. Implementations should prefer the generation whose work is most overdue.

async def list_adjustments(self, *, account_id: str, reporting_revision_ids: Sequence[str]) ‑> tuple[ReportingAdjustmentRecord, ...]
Expand source code
async def list_adjustments(
    self, *, account_id: str, reporting_revision_ids: Sequence[str]
) -> tuple[ReportingAdjustmentRecord, ...]: ...
async def list_all_configurations(self) ‑> tuple[ReportingConfiguration, ...]
Expand source code
async def list_all_configurations(self) -> tuple[ReportingConfiguration, ...]:
    """Enumerate retained generations for trusted service startup recovery.

    This administrative API is never exposed through buyer task handlers;
    those continue to use account-scoped reads after authorization.
    """
    raise NotImplementedError

Enumerate retained generations for trusted service startup recovery.

This administrative API is never exposed through buyer task handlers; those continue to use account-scoped reads after authorization.

async def list_configurations(self,
*,
caller: ReportingCaller,
delivery_config_ids: Sequence[str] | None = None) ‑> tuple[ReportingConfiguration, ...]
Expand source code
async def list_configurations(
    self, *, caller: ReportingCaller, delivery_config_ids: Sequence[str] | None = None
) -> tuple[ReportingConfiguration, ...]: ...
async def list_consumer_statuses(self,
*,
account_id: str,
consumer_id: str,
reporting_obligation_ids: Sequence[str] | None = None) ‑> tuple[ConsumerStatusRecord, ...]
Expand source code
async def list_consumer_statuses(
    self,
    *,
    account_id: str,
    consumer_id: str,
    reporting_obligation_ids: Sequence[str] | None = None,
) -> tuple[ConsumerStatusRecord, ...]: ...
async def list_revisions(self, *, account_id: str, reporting_obligation_id: str) ‑> tuple[ReportingRevisionRecord, ...]
Expand source code
async def list_revisions(
    self, *, account_id: str, reporting_obligation_id: str
) -> tuple[ReportingRevisionRecord, ...]: ...
async def open_snapshot(self,
*,
caller: ReportingCaller,
filters_fingerprint: str) ‑> LedgerSnapshot
Expand source code
async def open_snapshot(
    self, *, caller: ReportingCaller, filters_fingerprint: str
) -> LedgerSnapshot:
    """Take a consistent read boundary for one account and filter set."""
    ...

Take a consistent read boundary for one account and filter set.

async def put_configuration(self,
configuration: ReportingConfiguration) ‑> None
Expand source code
async def put_configuration(self, configuration: ReportingConfiguration) -> None:
    """Record an accepted configuration generation.

    A generation is immutable: re-putting one with changed content is a
    conflict, because a buyer that retained it derives expectations from it.
    """
    ...

Record an accepted configuration generation.

A generation is immutable: re-putting one with changed content is a conflict, because a buyer that retained it derives expectations from it.

async def read_page(self,
*,
snapshot: LedgerSnapshot,
consumer_id: str | None,
delivery_config_ids: Sequence[str] | None,
media_buy_ids: Sequence[str] | None,
offset: int,
limit: int,
changes_after_sequence: int | None) ‑> LedgerPage
Expand source code
async def read_page(
    self,
    *,
    snapshot: LedgerSnapshot,
    consumer_id: str | None,
    delivery_config_ids: Sequence[str] | None,
    media_buy_ids: Sequence[str] | None,
    offset: int,
    limit: int,
    changes_after_sequence: int | None,
) -> LedgerPage: ...
async def read_revision_rows(self,
*,
account_id: str,
reporting_revision_id: str,
cursor: str | None = None,
limit: int = 500) ‑> ReportingRowPage
Expand source code
async def read_revision_rows(
    self,
    *,
    account_id: str,
    reporting_revision_id: str,
    cursor: str | None = None,
    limit: int = 500,
) -> ReportingRowPage:
    """Walk one revision's frozen rows in stable order."""
    ...

Walk one revision's frozen rows in stable order.

async def record_consumer_status(self,
status: ConsumerStatusRecord) ‑> tuple[ConsumerStatusRecord, bool]
Expand source code
async def record_consumer_status(
    self, status: ConsumerStatusRecord
) -> tuple[ConsumerStatusRecord, bool]:
    """Append a consumer status statement, superseding the chain's leaf.

    Returns ``(record, recorded)`` where ``recorded`` is ``False`` for an
    exact idempotent replay.  Supersession is atomic: naming a stale or
    missing leaf must raise rather than fork the chain, so a successful
    retry cannot erase a recorded outage.
    """
    ...

Append a consumer status statement, superseding the chain's leaf.

Returns (record, recorded) where recorded is False for an exact idempotent replay. Supersession is atomic: naming a stale or missing leaf must raise rather than fork the chain, so a successful retry cannot erase a recorded outage.

async def release_period_close(self,
lease: LeasedConfiguration,
*,
worker_id: str) ‑> None
Expand source code
async def release_period_close(self, lease: LeasedConfiguration, *, worker_id: str) -> None: ...
async def resolve_consumer_status_replay(self,
status: ConsumerStatusRecord) ‑> ConsumerStatusRecord | None
Expand source code
async def resolve_consumer_status_replay(
    self, status: ConsumerStatusRecord
) -> ConsumerStatusRecord | None:
    """Return the stored row when this exact statement was already recorded.

    Exists so the ingest can answer "is this an exact retry?" *before* it
    validates anything time-dependent. ``record_consumer_status`` already
    makes that check, but it runs last, and some validation the ingest does
    first -- notably "``content_mismatch`` must name the revision the seller
    currently requires" -- has an answer that changes as the seller
    publishes. An exact retry sent after a restatement would then be
    rejected for a reason that did not apply when it was first accepted,
    and the spec's ``result_mapping`` requires ``unchanged``.

    Returns ``None`` when the id is unknown, so the caller proceeds to
    validate and record. Raises :class:`LedgerConflictError` with
    ``STATUS_IDENTITY_CONFLICT`` when the id exists with different content:
    reuse with changed content is still a conflict, and answering
    ``unchanged`` there would let a buyer rewrite a recorded statement.
    """
    ...

Return the stored row when this exact statement was already recorded.

Exists so the ingest can answer "is this an exact retry?" before it validates anything time-dependent. record_consumer_status already makes that check, but it runs last, and some validation the ingest does first – notably "content_mismatch must name the revision the seller currently requires" – has an answer that changes as the seller publishes. An exact retry sent after a restatement would then be rejected for a reason that did not apply when it was first accepted, and the spec's result_mapping requires unchanged.

Returns None when the id is unknown, so the caller proceeds to validate and record. Raises :class:LedgerConflictError with STATUS_IDENTITY_CONFLICT when the id exists with different content: reuse with changed content is still a conflict, and answering unchanged there would let a buyer rewrite a recorded statement.

async def retire_issue(self, *, issue_key: str, account_id: str, at: datetime) ‑> ReportingIssueLifecycle | None
Expand source code
async def retire_issue(
    self, *, issue_key: str, account_id: str, at: datetime
) -> ReportingIssueLifecycle | None:
    """Mark this condition's live occurrence ``resolved``.

    Called by the projection when the condition no longer holds. Returns
    ``None`` when there was nothing live to retire, so a repeated
    projection is convergent rather than an error.
    """
    ...

Mark this condition's live occurrence resolved.

Called by the projection when the condition no longer holds. Returns None when there was nothing live to retire, so a repeated projection is convergent rather than an error.

async def set_issue_state(self,
*,
issue_key: str,
account_id: str,
state: "Literal['acknowledged', 'waived']",
at: datetime,
external_ref: str | None = None) ‑> ReportingIssueLifecycle
Expand source code
async def set_issue_state(
    self,
    *,
    issue_key: str,
    account_id: str,
    state: Literal["acknowledged", "waived"],
    at: datetime,
    external_ref: str | None = None,
) -> ReportingIssueLifecycle:
    """Move a live occurrence forward, or attach an ``external_ref``.

    Deliberately cannot set ``resolved``. The spec forbids retiring a
    ``CONSUMER_STATUS_MISMATCH`` out of a degraded projection while the
    statement that caused it is still the consumer's current leaf, so
    resolution is not an operator action -- it is what the projection does
    when the condition actually clears (see :meth:`retire_issue`).
    Otherwise a seller could unilaterally erase a buyer-attributed
    disagreement, which is the one outcome this separately attributed loop
    exists to prevent.

    ``waived`` requires explicit off-protocol agreement by this consumer
    and seller for the exact issue, causing statement and diagnosed
    conflict. The adopter must retain its private consent audit before
    calling this method; ``external_ref`` is inert correlation, not proof.
    Rc.6 removes that issue and restores underlying seller health, without
    changing the consumer statement. Later statements or different
    conflicts are evaluated independently, never covered by this waiver.
    """
    ...

Move a live occurrence forward, or attach an external_ref.

Deliberately cannot set resolved. The spec forbids retiring a CONSUMER_STATUS_MISMATCH out of a degraded projection while the statement that caused it is still the consumer's current leaf, so resolution is not an operator action – it is what the projection does when the condition actually clears (see :meth:retire_issue). Otherwise a seller could unilaterally erase a buyer-attributed disagreement, which is the one outcome this separately attributed loop exists to prevent.

waived requires explicit off-protocol agreement by this consumer and seller for the exact issue, causing statement and diagnosed conflict. The adopter must retain its private consent audit before calling this method; external_ref is inert correlation, not proof. Rc.6 removes that issue and restores underlying seller health, without changing the consumer statement. Later statements or different conflicts are evaluated independently, never covered by this waiver.

async def set_revision_readable(self, *, account_id: str, reporting_revision_id: str, readable: bool) ‑> None
Expand source code
async def set_revision_readable(
    self, *, account_id: str, reporting_revision_id: str, readable: bool
) -> None:
    """Record that a revision's content is (no longer) readable.

    Retention expiry and storage loss are real; representing them is how an
    obligation becomes honestly ``action_required`` instead of staying
    ``complete`` over evidence nobody can read.
    """
    ...

Record that a revision's content is (no longer) readable.

Retention expiry and storage loss are real; representing them is how an obligation becomes honestly action_required instead of staying complete over evidence nobody can read.

class ReportingMaterializationAttempt (scope: ReportingDeliveryScope,
reporting_revision_id: str,
reporting_materialization_id: str,
attempt: int,
created_at: datetime,
*,
kind: "Literal['materialization_attempt']" = 'materialization_attempt')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)

ReportingMaterializationAttempt(scope: 'ReportingDeliveryScope', reporting_revision_id: 'str', reporting_materialization_id: 'str', attempt: 'int', created_at: 'datetime', *, kind: "Literal['materialization_attempt']" = 'materialization_attempt')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var attempt : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var created_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
prop key : ReportingMaterializationKey
Expand source code
@property
def key(self) -> ReportingMaterializationKey:
    return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var kind : Literal['materialization_attempt']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var reporting_materialization_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var reporting_revision_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var scope : ReportingDeliveryScope
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationAttempt(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    attempt: int
    created_at: datetime
    kind: Literal["materialization_attempt"] = field(
        default="materialization_attempt", kw_only=True
    )

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.attempt)
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
class ReportingMaterializationCheck (scope: ReportingDeliveryScope,
reporting_materialization_id: str,
check_id: str,
state: "Literal['readable', 'unavailable', 'corrupt']",
checked_at: datetime,
*,
kind: "Literal['materialization_check']" = 'materialization_check')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))

Append-only storage observations; they never mutate terminal evidence.

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var check_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))
var checked_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))
var kind : Literal['materialization_check']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))
var reporting_materialization_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))
var scope : ReportingDeliveryScope
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))
var state : Literal['readable', 'unavailable', 'corrupt']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationCheck(_ClosedValue):
    """Append-only storage observations; they never mutate terminal evidence."""

    scope: ReportingDeliveryScope
    reporting_materialization_id: str
    check_id: str
    state: Literal["readable", "unavailable", "corrupt"]
    checked_at: datetime
    kind: Literal["materialization_check"] = field(default="materialization_check", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        reporting_identifier(self.check_id, maximum=255)
        object.__setattr__(self, "checked_at", aware_utc(self.checked_at))
class ReportingMaterializationKey (principal: ReportingDeliveryPrincipal,
reporting_materialization_id: str)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationKey(_ClosedValue):
    principal: ReportingDeliveryPrincipal
    reporting_materialization_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)

ReportingMaterializationKey(principal: 'ReportingDeliveryPrincipal', reporting_materialization_id: 'str')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var principal : ReportingDeliveryPrincipal
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationKey(_ClosedValue):
    principal: ReportingDeliveryPrincipal
    reporting_materialization_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
var reporting_materialization_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationKey(_ClosedValue):
    principal: ReportingDeliveryPrincipal
    reporting_materialization_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
class ReportingMaterializationRecord (scope: ReportingDeliveryScope,
reporting_revision_id: str,
reporting_materialization_id: str,
status: "Literal['available', 'delivered', 'failed']",
completed_at: datetime,
resource: ReportingResourceRecord | None = None,
verification: ReportingVerificationRecord | None = None,
failure_code: MaterializationFailure | None = None,
*,
kind: "Literal['materialization']" = 'materialization')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)

One terminal outcome. The pending attempt is retained separately.

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var completed_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var failure_code : Literal['WRITE_FAILED', 'VERIFICATION_FAILED', 'CONTENT_CORRUPT', 'RESOURCE_UNAVAILABLE'] | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
prop key : ReportingMaterializationKey
Expand source code
@property
def key(self) -> ReportingMaterializationKey:
    return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var kind : Literal['materialization']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var reporting_materialization_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var reporting_revision_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var resource : ReportingResourceRecord | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var scope : ReportingDeliveryScope
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var status : Literal['available', 'delivered', 'failed']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
var verification : ReportingVerificationRecord | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationRecord(_ClosedValue):
    """One terminal outcome. The pending attempt is retained separately."""

    scope: ReportingDeliveryScope
    reporting_revision_id: str
    reporting_materialization_id: str
    status: Literal["available", "delivered", "failed"]
    completed_at: datetime
    resource: ReportingResourceRecord | None = None
    verification: ReportingVerificationRecord | None = None
    failure_code: MaterializationFailure | None = None
    kind: Literal["materialization"] = field(default="materialization", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        object.__setattr__(self, "completed_at", aware_utc(self.completed_at))
        if self.status == "failed":
            if (
                self.failure_code is None
                or self.resource is not None
                or self.verification is not None
            ):
                raise ValueError(
                    "failed materializations retain only a safe failure classification"
                )
        elif self.resource is None or self.verification is None or self.failure_code is not None:
            raise ValueError(
                "successful materializations require resource and verification evidence"
            )

    @property
    def key(self) -> ReportingMaterializationKey:
        return ReportingMaterializationKey(self.scope.principal, self.reporting_materialization_id)
class ReportingMaterializationStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingMaterializationStore(Protocol):
    async def commit_materialization_attempt(
        self, record: ReportingMaterializationAttempt
    ) -> tuple[ReportingMaterializationAttempt, bool]: ...

    async def commit_materialization(
        self, record: ReportingMaterializationRecord
    ) -> tuple[ReportingMaterializationRecord, bool]: ...

    async def record_materialization_check(
        self, record: ReportingMaterializationCheck
    ) -> tuple[ReportingMaterializationCheck, bool]: ...

    async def get_materialization(
        self, key: ReportingMaterializationKey
    ) -> ReportingMaterializationView | None: ...

Base class for protocol classes.

Protocol classes are defined as::

class Proto(Protocol):
    def meth(self) -> int:
        ...

Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).

For example::

class C:
    def meth(self) -> int:
        return 0

def func(x: Proto) -> int:
    return x.meth()

func(C())  # Passes static type check

See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::

class GenProto(Protocol[T]):
    def meth(self) -> T:
        ...

Ancestors

  • typing.Protocol
  • typing.Generic

Subclasses

Methods

async def commit_materialization(self,
record: ReportingMaterializationRecord) ‑> tuple[ReportingMaterializationRecord, bool]
Expand source code
async def commit_materialization(
    self, record: ReportingMaterializationRecord
) -> tuple[ReportingMaterializationRecord, bool]: ...
async def commit_materialization_attempt(self,
record: ReportingMaterializationAttempt) ‑> tuple[ReportingMaterializationAttempt, bool]
Expand source code
async def commit_materialization_attempt(
    self, record: ReportingMaterializationAttempt
) -> tuple[ReportingMaterializationAttempt, bool]: ...
async def get_materialization(self,
key: ReportingMaterializationKey) ‑> ReportingMaterializationView | None
Expand source code
async def get_materialization(
    self, key: ReportingMaterializationKey
) -> ReportingMaterializationView | None: ...
async def record_materialization_check(self,
record: ReportingMaterializationCheck) ‑> tuple[ReportingMaterializationCheck, bool]
Expand source code
async def record_materialization_check(
    self, record: ReportingMaterializationCheck
) -> tuple[ReportingMaterializationCheck, bool]: ...
class ReportingMaterializationView (attempt: ReportingMaterializationAttempt,
binding: ReportingDestinationBinding,
outcome: ReportingMaterializationRecord | None,
checks: tuple[ReportingMaterializationCheck, ...])
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationView:
    attempt: ReportingMaterializationAttempt
    binding: ReportingDestinationBinding
    outcome: ReportingMaterializationRecord | None
    checks: tuple[ReportingMaterializationCheck, ...]

    @property
    def check(self) -> ReportingMaterializationCheck | None:
        return max(self.checks, key=lambda item: item.checked_at, default=None)

    def readable_at(self, at: datetime) -> bool:
        outcome = self.outcome
        check = max(
            (item for item in self.checks if item.checked_at <= at),
            key=lambda item: item.checked_at,
            default=None,
        )
        return bool(
            outcome is not None
            and outcome.status in {"available", "delivered"}
            and outcome.resource is not None
            and outcome.completed_at <= at < outcome.resource.expires_at
            and (check is None or check.state == "readable")
        )

    def to_wire(self) -> dict[str, Any]:
        """An inert projection for future handlers. Never exposes the trusted reference."""
        attempt, binding, outcome = self.attempt, self.binding, self.outcome
        generation = attempt.scope.generation_key
        result: dict[str, Any] = {
            "reporting_materialization_id": attempt.reporting_materialization_id,
            "reporting_revision_id": attempt.reporting_revision_id,
            "reporting_obligation_id": attempt.scope.reporting_obligation_id,
            "delivery_config_id": generation.delivery_config_id,
            "delivery_config_version": generation.delivery_config_version,
            "destination_ref": binding.destination_ref,
            "feed_purpose": binding.feed_purpose,
            "method": binding.method,
            "transport": binding.transport,
            "attempt": attempt.attempt,
            "status": outcome.status if outcome is not None else "pending",
            "created_at": iso(attempt.created_at),
        }
        if outcome is None:
            return result
        if outcome.status == "failed":
            result.update(failed_at=iso(outcome.completed_at), failure_code=outcome.failure_code)
            return result
        resource, verification = outcome.resource, outcome.verification
        assert resource is not None and verification is not None
        descriptor = {
            key: value
            for key, value in asdict(resource).items()
            if value is not None and key != "object_refs"
        }
        descriptor["expires_at"] = iso(resource.expires_at)
        descriptor["reader_compatibility"] = list(resource.reader_compatibility)
        if resource.kind == "manifest":
            descriptor["manifest_version"] = "1.0"
        evidence: dict[str, Any] = {
            "verified_at": iso(verification.verified_at),
            "verification_path": verification.verification_path,
            "verification_profile": verification.verification_profile,
            "row_count": verification.row_count,
            "control_totals": totals_to_wire(verification.control_totals),
        }
        if verification.canonical_content_digest is not None:
            evidence["canonical_content_digest"] = verification.canonical_content_digest.to_wire()
        if verification.physical_checksums:
            evidence["physical_checksums"] = [
                asdict(item) for item in verification.physical_checksums
            ]
        if verification.native_version_ref is not None:
            evidence["native_commit_evidence"] = {
                "native_version_ref": verification.native_version_ref,
                "observed_through": verification.native_observed_through,
            }
        result.update(
            ready_at=iso(outcome.completed_at), resource=descriptor, verification=evidence
        )
        return result

ReportingMaterializationView(attempt: 'ReportingMaterializationAttempt', binding: 'ReportingDestinationBinding', outcome: 'ReportingMaterializationRecord | None', checks: 'tuple[ReportingMaterializationCheck, …]')

Instance variables

var attempt : ReportingMaterializationAttempt
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationView:
    attempt: ReportingMaterializationAttempt
    binding: ReportingDestinationBinding
    outcome: ReportingMaterializationRecord | None
    checks: tuple[ReportingMaterializationCheck, ...]

    @property
    def check(self) -> ReportingMaterializationCheck | None:
        return max(self.checks, key=lambda item: item.checked_at, default=None)

    def readable_at(self, at: datetime) -> bool:
        outcome = self.outcome
        check = max(
            (item for item in self.checks if item.checked_at <= at),
            key=lambda item: item.checked_at,
            default=None,
        )
        return bool(
            outcome is not None
            and outcome.status in {"available", "delivered"}
            and outcome.resource is not None
            and outcome.completed_at <= at < outcome.resource.expires_at
            and (check is None or check.state == "readable")
        )

    def to_wire(self) -> dict[str, Any]:
        """An inert projection for future handlers. Never exposes the trusted reference."""
        attempt, binding, outcome = self.attempt, self.binding, self.outcome
        generation = attempt.scope.generation_key
        result: dict[str, Any] = {
            "reporting_materialization_id": attempt.reporting_materialization_id,
            "reporting_revision_id": attempt.reporting_revision_id,
            "reporting_obligation_id": attempt.scope.reporting_obligation_id,
            "delivery_config_id": generation.delivery_config_id,
            "delivery_config_version": generation.delivery_config_version,
            "destination_ref": binding.destination_ref,
            "feed_purpose": binding.feed_purpose,
            "method": binding.method,
            "transport": binding.transport,
            "attempt": attempt.attempt,
            "status": outcome.status if outcome is not None else "pending",
            "created_at": iso(attempt.created_at),
        }
        if outcome is None:
            return result
        if outcome.status == "failed":
            result.update(failed_at=iso(outcome.completed_at), failure_code=outcome.failure_code)
            return result
        resource, verification = outcome.resource, outcome.verification
        assert resource is not None and verification is not None
        descriptor = {
            key: value
            for key, value in asdict(resource).items()
            if value is not None and key != "object_refs"
        }
        descriptor["expires_at"] = iso(resource.expires_at)
        descriptor["reader_compatibility"] = list(resource.reader_compatibility)
        if resource.kind == "manifest":
            descriptor["manifest_version"] = "1.0"
        evidence: dict[str, Any] = {
            "verified_at": iso(verification.verified_at),
            "verification_path": verification.verification_path,
            "verification_profile": verification.verification_profile,
            "row_count": verification.row_count,
            "control_totals": totals_to_wire(verification.control_totals),
        }
        if verification.canonical_content_digest is not None:
            evidence["canonical_content_digest"] = verification.canonical_content_digest.to_wire()
        if verification.physical_checksums:
            evidence["physical_checksums"] = [
                asdict(item) for item in verification.physical_checksums
            ]
        if verification.native_version_ref is not None:
            evidence["native_commit_evidence"] = {
                "native_version_ref": verification.native_version_ref,
                "observed_through": verification.native_observed_through,
            }
        result.update(
            ready_at=iso(outcome.completed_at), resource=descriptor, verification=evidence
        )
        return result
var binding : ReportingDestinationBinding
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationView:
    attempt: ReportingMaterializationAttempt
    binding: ReportingDestinationBinding
    outcome: ReportingMaterializationRecord | None
    checks: tuple[ReportingMaterializationCheck, ...]

    @property
    def check(self) -> ReportingMaterializationCheck | None:
        return max(self.checks, key=lambda item: item.checked_at, default=None)

    def readable_at(self, at: datetime) -> bool:
        outcome = self.outcome
        check = max(
            (item for item in self.checks if item.checked_at <= at),
            key=lambda item: item.checked_at,
            default=None,
        )
        return bool(
            outcome is not None
            and outcome.status in {"available", "delivered"}
            and outcome.resource is not None
            and outcome.completed_at <= at < outcome.resource.expires_at
            and (check is None or check.state == "readable")
        )

    def to_wire(self) -> dict[str, Any]:
        """An inert projection for future handlers. Never exposes the trusted reference."""
        attempt, binding, outcome = self.attempt, self.binding, self.outcome
        generation = attempt.scope.generation_key
        result: dict[str, Any] = {
            "reporting_materialization_id": attempt.reporting_materialization_id,
            "reporting_revision_id": attempt.reporting_revision_id,
            "reporting_obligation_id": attempt.scope.reporting_obligation_id,
            "delivery_config_id": generation.delivery_config_id,
            "delivery_config_version": generation.delivery_config_version,
            "destination_ref": binding.destination_ref,
            "feed_purpose": binding.feed_purpose,
            "method": binding.method,
            "transport": binding.transport,
            "attempt": attempt.attempt,
            "status": outcome.status if outcome is not None else "pending",
            "created_at": iso(attempt.created_at),
        }
        if outcome is None:
            return result
        if outcome.status == "failed":
            result.update(failed_at=iso(outcome.completed_at), failure_code=outcome.failure_code)
            return result
        resource, verification = outcome.resource, outcome.verification
        assert resource is not None and verification is not None
        descriptor = {
            key: value
            for key, value in asdict(resource).items()
            if value is not None and key != "object_refs"
        }
        descriptor["expires_at"] = iso(resource.expires_at)
        descriptor["reader_compatibility"] = list(resource.reader_compatibility)
        if resource.kind == "manifest":
            descriptor["manifest_version"] = "1.0"
        evidence: dict[str, Any] = {
            "verified_at": iso(verification.verified_at),
            "verification_path": verification.verification_path,
            "verification_profile": verification.verification_profile,
            "row_count": verification.row_count,
            "control_totals": totals_to_wire(verification.control_totals),
        }
        if verification.canonical_content_digest is not None:
            evidence["canonical_content_digest"] = verification.canonical_content_digest.to_wire()
        if verification.physical_checksums:
            evidence["physical_checksums"] = [
                asdict(item) for item in verification.physical_checksums
            ]
        if verification.native_version_ref is not None:
            evidence["native_commit_evidence"] = {
                "native_version_ref": verification.native_version_ref,
                "observed_through": verification.native_observed_through,
            }
        result.update(
            ready_at=iso(outcome.completed_at), resource=descriptor, verification=evidence
        )
        return result
prop check : ReportingMaterializationCheck | None
Expand source code
@property
def check(self) -> ReportingMaterializationCheck | None:
    return max(self.checks, key=lambda item: item.checked_at, default=None)
var checks : tuple[ReportingMaterializationCheck, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationView:
    attempt: ReportingMaterializationAttempt
    binding: ReportingDestinationBinding
    outcome: ReportingMaterializationRecord | None
    checks: tuple[ReportingMaterializationCheck, ...]

    @property
    def check(self) -> ReportingMaterializationCheck | None:
        return max(self.checks, key=lambda item: item.checked_at, default=None)

    def readable_at(self, at: datetime) -> bool:
        outcome = self.outcome
        check = max(
            (item for item in self.checks if item.checked_at <= at),
            key=lambda item: item.checked_at,
            default=None,
        )
        return bool(
            outcome is not None
            and outcome.status in {"available", "delivered"}
            and outcome.resource is not None
            and outcome.completed_at <= at < outcome.resource.expires_at
            and (check is None or check.state == "readable")
        )

    def to_wire(self) -> dict[str, Any]:
        """An inert projection for future handlers. Never exposes the trusted reference."""
        attempt, binding, outcome = self.attempt, self.binding, self.outcome
        generation = attempt.scope.generation_key
        result: dict[str, Any] = {
            "reporting_materialization_id": attempt.reporting_materialization_id,
            "reporting_revision_id": attempt.reporting_revision_id,
            "reporting_obligation_id": attempt.scope.reporting_obligation_id,
            "delivery_config_id": generation.delivery_config_id,
            "delivery_config_version": generation.delivery_config_version,
            "destination_ref": binding.destination_ref,
            "feed_purpose": binding.feed_purpose,
            "method": binding.method,
            "transport": binding.transport,
            "attempt": attempt.attempt,
            "status": outcome.status if outcome is not None else "pending",
            "created_at": iso(attempt.created_at),
        }
        if outcome is None:
            return result
        if outcome.status == "failed":
            result.update(failed_at=iso(outcome.completed_at), failure_code=outcome.failure_code)
            return result
        resource, verification = outcome.resource, outcome.verification
        assert resource is not None and verification is not None
        descriptor = {
            key: value
            for key, value in asdict(resource).items()
            if value is not None and key != "object_refs"
        }
        descriptor["expires_at"] = iso(resource.expires_at)
        descriptor["reader_compatibility"] = list(resource.reader_compatibility)
        if resource.kind == "manifest":
            descriptor["manifest_version"] = "1.0"
        evidence: dict[str, Any] = {
            "verified_at": iso(verification.verified_at),
            "verification_path": verification.verification_path,
            "verification_profile": verification.verification_profile,
            "row_count": verification.row_count,
            "control_totals": totals_to_wire(verification.control_totals),
        }
        if verification.canonical_content_digest is not None:
            evidence["canonical_content_digest"] = verification.canonical_content_digest.to_wire()
        if verification.physical_checksums:
            evidence["physical_checksums"] = [
                asdict(item) for item in verification.physical_checksums
            ]
        if verification.native_version_ref is not None:
            evidence["native_commit_evidence"] = {
                "native_version_ref": verification.native_version_ref,
                "observed_through": verification.native_observed_through,
            }
        result.update(
            ready_at=iso(outcome.completed_at), resource=descriptor, verification=evidence
        )
        return result
var outcome : ReportingMaterializationRecord | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingMaterializationView:
    attempt: ReportingMaterializationAttempt
    binding: ReportingDestinationBinding
    outcome: ReportingMaterializationRecord | None
    checks: tuple[ReportingMaterializationCheck, ...]

    @property
    def check(self) -> ReportingMaterializationCheck | None:
        return max(self.checks, key=lambda item: item.checked_at, default=None)

    def readable_at(self, at: datetime) -> bool:
        outcome = self.outcome
        check = max(
            (item for item in self.checks if item.checked_at <= at),
            key=lambda item: item.checked_at,
            default=None,
        )
        return bool(
            outcome is not None
            and outcome.status in {"available", "delivered"}
            and outcome.resource is not None
            and outcome.completed_at <= at < outcome.resource.expires_at
            and (check is None or check.state == "readable")
        )

    def to_wire(self) -> dict[str, Any]:
        """An inert projection for future handlers. Never exposes the trusted reference."""
        attempt, binding, outcome = self.attempt, self.binding, self.outcome
        generation = attempt.scope.generation_key
        result: dict[str, Any] = {
            "reporting_materialization_id": attempt.reporting_materialization_id,
            "reporting_revision_id": attempt.reporting_revision_id,
            "reporting_obligation_id": attempt.scope.reporting_obligation_id,
            "delivery_config_id": generation.delivery_config_id,
            "delivery_config_version": generation.delivery_config_version,
            "destination_ref": binding.destination_ref,
            "feed_purpose": binding.feed_purpose,
            "method": binding.method,
            "transport": binding.transport,
            "attempt": attempt.attempt,
            "status": outcome.status if outcome is not None else "pending",
            "created_at": iso(attempt.created_at),
        }
        if outcome is None:
            return result
        if outcome.status == "failed":
            result.update(failed_at=iso(outcome.completed_at), failure_code=outcome.failure_code)
            return result
        resource, verification = outcome.resource, outcome.verification
        assert resource is not None and verification is not None
        descriptor = {
            key: value
            for key, value in asdict(resource).items()
            if value is not None and key != "object_refs"
        }
        descriptor["expires_at"] = iso(resource.expires_at)
        descriptor["reader_compatibility"] = list(resource.reader_compatibility)
        if resource.kind == "manifest":
            descriptor["manifest_version"] = "1.0"
        evidence: dict[str, Any] = {
            "verified_at": iso(verification.verified_at),
            "verification_path": verification.verification_path,
            "verification_profile": verification.verification_profile,
            "row_count": verification.row_count,
            "control_totals": totals_to_wire(verification.control_totals),
        }
        if verification.canonical_content_digest is not None:
            evidence["canonical_content_digest"] = verification.canonical_content_digest.to_wire()
        if verification.physical_checksums:
            evidence["physical_checksums"] = [
                asdict(item) for item in verification.physical_checksums
            ]
        if verification.native_version_ref is not None:
            evidence["native_commit_evidence"] = {
                "native_version_ref": verification.native_version_ref,
                "observed_through": verification.native_observed_through,
            }
        result.update(
            ready_at=iso(outcome.completed_at), resource=descriptor, verification=evidence
        )
        return result

Methods

def readable_at(self, at: datetime) ‑> bool
Expand source code
def readable_at(self, at: datetime) -> bool:
    outcome = self.outcome
    check = max(
        (item for item in self.checks if item.checked_at <= at),
        key=lambda item: item.checked_at,
        default=None,
    )
    return bool(
        outcome is not None
        and outcome.status in {"available", "delivered"}
        and outcome.resource is not None
        and outcome.completed_at <= at < outcome.resource.expires_at
        and (check is None or check.state == "readable")
    )
def to_wire(self) ‑> dict[str, typing.Any]
Expand source code
def to_wire(self) -> dict[str, Any]:
    """An inert projection for future handlers. Never exposes the trusted reference."""
    attempt, binding, outcome = self.attempt, self.binding, self.outcome
    generation = attempt.scope.generation_key
    result: dict[str, Any] = {
        "reporting_materialization_id": attempt.reporting_materialization_id,
        "reporting_revision_id": attempt.reporting_revision_id,
        "reporting_obligation_id": attempt.scope.reporting_obligation_id,
        "delivery_config_id": generation.delivery_config_id,
        "delivery_config_version": generation.delivery_config_version,
        "destination_ref": binding.destination_ref,
        "feed_purpose": binding.feed_purpose,
        "method": binding.method,
        "transport": binding.transport,
        "attempt": attempt.attempt,
        "status": outcome.status if outcome is not None else "pending",
        "created_at": iso(attempt.created_at),
    }
    if outcome is None:
        return result
    if outcome.status == "failed":
        result.update(failed_at=iso(outcome.completed_at), failure_code=outcome.failure_code)
        return result
    resource, verification = outcome.resource, outcome.verification
    assert resource is not None and verification is not None
    descriptor = {
        key: value
        for key, value in asdict(resource).items()
        if value is not None and key != "object_refs"
    }
    descriptor["expires_at"] = iso(resource.expires_at)
    descriptor["reader_compatibility"] = list(resource.reader_compatibility)
    if resource.kind == "manifest":
        descriptor["manifest_version"] = "1.0"
    evidence: dict[str, Any] = {
        "verified_at": iso(verification.verified_at),
        "verification_path": verification.verification_path,
        "verification_profile": verification.verification_profile,
        "row_count": verification.row_count,
        "control_totals": totals_to_wire(verification.control_totals),
    }
    if verification.canonical_content_digest is not None:
        evidence["canonical_content_digest"] = verification.canonical_content_digest.to_wire()
    if verification.physical_checksums:
        evidence["physical_checksums"] = [
            asdict(item) for item in verification.physical_checksums
        ]
    if verification.native_version_ref is not None:
        evidence["native_commit_evidence"] = {
            "native_version_ref": verification.native_version_ref,
            "observed_through": verification.native_observed_through,
        }
    result.update(
        ready_at=iso(outcome.completed_at), resource=descriptor, verification=evidence
    )
    return result

An inert projection for future handlers. Never exposes the trusted reference.

class ReportingObligationDeliveryRecord (scope: ReportingDeliveryScope,
currency: str,
resource_retained_until: datetime,
created_at: datetime,
*,
kind: "Literal['obligation_delivery']" = 'obligation_delivery')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingObligationDeliveryRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    currency: str
    resource_retained_until: datetime
    created_at: datetime
    kind: Literal["obligation_delivery"] = field(default="obligation_delivery", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        validate_currency(self.currency)
        object.__setattr__(self, "resource_retained_until", aware_utc(self.resource_retained_until))
        object.__setattr__(self, "created_at", aware_utc(self.created_at))

ReportingObligationDeliveryRecord(scope: 'ReportingDeliveryScope', currency: 'str', resource_retained_until: 'datetime', created_at: 'datetime', *, kind: "Literal['obligation_delivery']" = 'obligation_delivery')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var created_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingObligationDeliveryRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    currency: str
    resource_retained_until: datetime
    created_at: datetime
    kind: Literal["obligation_delivery"] = field(default="obligation_delivery", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        validate_currency(self.currency)
        object.__setattr__(self, "resource_retained_until", aware_utc(self.resource_retained_until))
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
var currency : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingObligationDeliveryRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    currency: str
    resource_retained_until: datetime
    created_at: datetime
    kind: Literal["obligation_delivery"] = field(default="obligation_delivery", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        validate_currency(self.currency)
        object.__setattr__(self, "resource_retained_until", aware_utc(self.resource_retained_until))
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
var kind : Literal['obligation_delivery']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingObligationDeliveryRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    currency: str
    resource_retained_until: datetime
    created_at: datetime
    kind: Literal["obligation_delivery"] = field(default="obligation_delivery", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        validate_currency(self.currency)
        object.__setattr__(self, "resource_retained_until", aware_utc(self.resource_retained_until))
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
var resource_retained_until : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingObligationDeliveryRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    currency: str
    resource_retained_until: datetime
    created_at: datetime
    kind: Literal["obligation_delivery"] = field(default="obligation_delivery", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        validate_currency(self.currency)
        object.__setattr__(self, "resource_retained_until", aware_utc(self.resource_retained_until))
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
var scope : ReportingDeliveryScope
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingObligationDeliveryRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    currency: str
    resource_retained_until: datetime
    created_at: datetime
    kind: Literal["obligation_delivery"] = field(default="obligation_delivery", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        validate_currency(self.currency)
        object.__setattr__(self, "resource_retained_until", aware_utc(self.resource_retained_until))
        object.__setattr__(self, "created_at", aware_utc(self.created_at))
class ReportingObligationRecord (reporting_obligation_id: str,
account_id: str,
consumer_id: str,
delivery_config_id: str,
delivery_config_version: int,
report_definition_id: str,
reporting_profile: str,
feed_purpose: str,
period: ReportingPeriodBoundary,
scope_resolved_at: datetime,
media_buy_ids: tuple[str, ...],
required_finality: ReportingFinality,
automated_recovery_deadline_at: datetime,
schedule: ReportingScheduleSpec,
coverage_status: "Literal['full', 'partial', 'none', 'unknown']" = 'full',
package_ids: tuple[str, ...] = (),
definition: ReportingDefinitionBinding | None = None,
created_at: datetime = <factory>,
currency: str | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingObligationRecord:
    """What *should* exist for one configuration generation and period.

    Committed at the period boundary, independently of whether source data is
    available and before any revision.  ``scope_resolved_at`` equals the period
    end: that is the instant the media-buy denominator froze, and a coverage
    evaluation at any other instant is describing a different scope.
    """

    reporting_obligation_id: str
    account_id: str
    consumer_id: str
    delivery_config_id: str
    delivery_config_version: int
    report_definition_id: str
    reporting_profile: str
    feed_purpose: str
    period: ReportingPeriodBoundary
    scope_resolved_at: datetime
    media_buy_ids: tuple[str, ...]
    required_finality: ReportingFinality
    automated_recovery_deadline_at: datetime
    schedule: ReportingScheduleSpec
    coverage_status: Literal["full", "partial", "none", "unknown"] = "full"
    package_ids: tuple[str, ...] = ()
    definition: ReportingDefinitionBinding | None = None
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    # None describes legacy evidence only. New writes must freeze a currency;
    # neither a restart nor a source response may fill an unknown historical one.
    currency: str | None = None

    @property
    def generation_key(self) -> ReportingConfigurationGenerationKey:
        return ReportingConfigurationGenerationKey(
            account_id=self.account_id,
            consumer_id=self.consumer_id,
            delivery_config_id=self.delivery_config_id,
            delivery_config_version=self.delivery_config_version,
        )

    def __post_init__(self) -> None:
        principal_reference(self.account_id)
        consumer_reference(self.consumer_id)
        if self.currency is not None:
            validate_currency(self.currency)
            if self.definition is not None:
                validate_currency_units(
                    self.currency,
                    (
                        *self.definition.monetary_metric_units,
                        *self.definition.monetary_control_total_units,
                    ),
                )
        object.__setattr__(self, "media_buy_ids", tuple(self.media_buy_ids))
        object.__setattr__(self, "package_ids", tuple(self.package_ids))
        if _utc(self.scope_resolved_at) != _utc(self.period.end):
            raise ValueError(
                "scope_resolved_at must equal the period end; the denominator froze there "
                "and a coverage evaluation at another instant describes another scope"
            )
        if _utc(self.automated_recovery_deadline_at) < _utc(self.period.expected_at):
            raise ValueError("the automated recovery deadline cannot precede expected_at")

What should exist for one configuration generation and period.

Committed at the period boundary, independently of whether source data is available and before any revision. scope_resolved_at equals the period end: that is the instant the media-buy denominator froze, and a coverage evaluation at any other instant is describing a different scope.

Instance variables

var account_id : str
var automated_recovery_deadline_at : datetime.datetime
var consumer_id : str
var coverage_status : Literal['full', 'partial', 'none', 'unknown']
var created_at : datetime.datetime
var currency : str | None
var definition : ReportingDefinitionBinding | None
var delivery_config_id : str
var delivery_config_version : int
var feed_purpose : str
prop generation_key : ReportingConfigurationGenerationKey
Expand source code
@property
def generation_key(self) -> ReportingConfigurationGenerationKey:
    return ReportingConfigurationGenerationKey(
        account_id=self.account_id,
        consumer_id=self.consumer_id,
        delivery_config_id=self.delivery_config_id,
        delivery_config_version=self.delivery_config_version,
    )
var media_buy_ids : tuple[str, ...]
var package_ids : tuple[str, ...]
var period : ReportingPeriodBoundary
var report_definition_id : str
var reporting_obligation_id : str
var reporting_profile : str
var required_finality : Literal['snapshot', 'official']
var schedule : ReportingScheduleSpec
var scope_resolved_at : datetime.datetime
class ReportingPeriodBoundary (period_key: str,
start: datetime,
end: datetime,
source_timezone: str,
expected_at: datetime)
Expand source code
@dataclass(frozen=True)
class ReportingPeriodBoundary:
    """One derived half-open period plus the instant its report becomes late."""

    period_key: str
    start: datetime
    end: datetime
    source_timezone: str
    expected_at: datetime

    def __post_init__(self) -> None:
        if _utc(self.end) <= _utc(self.start):
            raise ValueError("a reporting period must be nonempty")
        if _utc(self.expected_at) < _utc(self.end):
            raise ValueError("expected_at cannot precede the period end")

One derived half-open period plus the instant its report becomes late.

Instance variables

var end : datetime.datetime
var expected_at : datetime.datetime
var period_key : str
var source_timezone : str
var start : datetime.datetime
class ReportingPhysicalChecksum (object_ref: str, algorithm: "Literal['sha256', 'sha512']", value: str)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingPhysicalChecksum(_ClosedValue):
    object_ref: str
    algorithm: Literal["sha256", "sha512"]
    value: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        file_object_reference(self.object_ref)
        size = 64 if self.algorithm == "sha256" else 128
        if (
            not isinstance(self.value, str)
            or re.fullmatch(rf"[a-fA-F0-9]{{{size}}}", self.value) is None
        ):
            raise ValueError("physical checksum length must match its algorithm")

ReportingPhysicalChecksum(object_ref: 'str', algorithm: "Literal['sha256', 'sha512']", value: 'str')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var algorithm : Literal['sha256', 'sha512']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingPhysicalChecksum(_ClosedValue):
    object_ref: str
    algorithm: Literal["sha256", "sha512"]
    value: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        file_object_reference(self.object_ref)
        size = 64 if self.algorithm == "sha256" else 128
        if (
            not isinstance(self.value, str)
            or re.fullmatch(rf"[a-fA-F0-9]{{{size}}}", self.value) is None
        ):
            raise ValueError("physical checksum length must match its algorithm")
var object_ref : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingPhysicalChecksum(_ClosedValue):
    object_ref: str
    algorithm: Literal["sha256", "sha512"]
    value: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        file_object_reference(self.object_ref)
        size = 64 if self.algorithm == "sha256" else 128
        if (
            not isinstance(self.value, str)
            or re.fullmatch(rf"[a-fA-F0-9]{{{size}}}", self.value) is None
        ):
            raise ValueError("physical checksum length must match its algorithm")
var value : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingPhysicalChecksum(_ClosedValue):
    object_ref: str
    algorithm: Literal["sha256", "sha512"]
    value: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        file_object_reference(self.object_ref)
        size = 64 if self.algorithm == "sha256" else 128
        if (
            not isinstance(self.value, str)
            or re.fullmatch(rf"[a-fA-F0-9]{{{size}}}", self.value) is None
        ):
            raise ValueError("physical checksum length must match its algorithm")
class ReportingProducer (*,
source: ReportingSourceExecutor,
offerings: ProducerOfferings,
store: ReportingLedgerStore,
object_reader: ReportingSourceStagedObjectReader | None = None,
escalation: ReportingDeliveryEscalation | None = None,
worker_id: str = 'reporting-producer',
lease_seconds: float = 60.0,
max_periods_per_turn: int = 64,
clock: Callable[[], datetime] | None = None,
currency_resolver: CurrencyResolver | None = None,
revision_verifier: ReportingRevisionVerifier | None = None,
retry_initial_delay: timedelta = datetime.timedelta(seconds=60),
retry_max_delay: timedelta = datetime.timedelta(seconds=1800),
post_deadline_retry_interval: timedelta = datetime.timedelta(seconds=3600),
retry_store: RetryScheduleStore | None = None)
Expand source code
class ReportingProducer:
    """Closes periods, drives the source, and commits immutable revisions."""

    def __init__(
        self,
        *,
        source: ReportingSourceExecutor,
        offerings: ProducerOfferings,
        store: ReportingLedgerStore,
        object_reader: ReportingSourceStagedObjectReader | None = None,
        escalation: ReportingDeliveryEscalation | None = None,
        worker_id: str = "reporting-producer",
        lease_seconds: float = 60.0,
        max_periods_per_turn: int = 64,
        clock: Callable[[], datetime] | None = None,
        currency_resolver: CurrencyResolver | None = None,
        revision_verifier: ReportingRevisionVerifier | None = None,
        retry_initial_delay: timedelta = timedelta(minutes=1),
        retry_max_delay: timedelta = timedelta(minutes=30),
        post_deadline_retry_interval: timedelta = timedelta(hours=1),
        retry_store: RetryScheduleStore | None = None,
    ) -> None:
        if (
            retry_initial_delay <= timedelta(0)
            or retry_max_delay < retry_initial_delay
            or post_deadline_retry_interval <= timedelta(0)
        ):
            raise ValueError("retry delays must be positive and maximum must exceed initial")
        if retry_store is None:
            if not isinstance(store, RetryScheduleStore):
                raise TypeError(
                    "ReportingProducer requires a RetryScheduleStore to persist retry state"
                )
            retry_store = store
        self._source = source
        self._offerings = offerings
        self._store = store
        self._object_reader = object_reader
        self._escalation = escalation or ReportingDeliveryEscalation()
        self._worker_id = worker_id
        self._lease_seconds = lease_seconds
        self._max_periods_per_turn = max_periods_per_turn
        self._clock = clock or (lambda: datetime.now(timezone.utc))
        self._currency_resolver = (
            currency_resolver
            if currency_resolver is not None
            else FixedCurrencyResolver(offerings.currency)
        )
        self._revision_verifier = revision_verifier
        self._retry_initial_delay = retry_initial_delay
        self._retry_max_delay = retry_max_delay
        self._post_deadline_retry_interval = post_deadline_retry_interval
        self._retry_store = retry_store

    @property
    def store(self) -> ReportingLedgerStore:
        return self._store

    @property
    def escalation(self) -> ReportingDeliveryEscalation:
        """The advertised escalation commitment, for the status handler.

        Pass the same object to :class:`~adcp.reporting.ledger.status.ReportingStatusHandler`
        so the projection honours exactly the window the seller published. A
        handler with a different window than the capability block would escalate
        on a clock no buyer can see.
        """
        return self._escalation

    def advertised_reporting_delivery(
        self,
        *,
        consumer_status_task: bool,
        offerings: Sequence[Mapping[str, Any]],
        automated_recovery_window: timedelta,
        status_retention_days: int,
        extra: Mapping[str, Any] | None = None,
    ) -> dict[str, Any]:
        """The complete ``media_buy.reporting_delivery`` block for this producer.

        Returns a *whole* capability document, not a fragment, so the result can
        be validated against ``core/reporting-delivery-capabilities.json``
        before it is published. A fragment would push six required fields onto
        the caller to remember, and an under-filled capability block is exactly
        the kind of thing that passes review and fails a buyer's validator.

        The seller supplies what only it knows -- its ``offerings``, its
        seller-wide recovery window and retention. This method supplies the task
        names and the Reliable Reporting declarations, because those follow from
        the producer actually running rather than from configuration.

        ``consumer_status_task`` is an explicit argument rather than inferred:
        advertising it while the ingest is disabled is the half-implemented loop
        this module's docstring warns about, and a buyer that can file
        statements nobody reads believes it has told you.
        """
        return _advertised_reporting_delivery(
            escalation=self._escalation,
            consumer_status_task=consumer_status_task,
            offerings=offerings,
            automated_recovery_window=automated_recovery_window,
            status_retention_days=status_retention_days,
            extra=extra,
        )

    # -- the worker turn -------------------------------------------------

    async def run_worker(self) -> WorkerTurn:
        """Run one leased turn. Safe to call from cron, a loop, or a supervisor.

        Returns immediately with an empty turn when nothing is leasable, so a
        caller can back off rather than spin. Standalone PostgreSQL turns retry
        deadlocks and lock timeouts at most twice, after rollback and fenced
        release. Each retry must acquire a new lease; other failures propagate.
        """
        from adcp.reporting.ledger.pg import PgReportingLedgerStore

        retryable = (
            self._store._worker_lock_errors()
            if isinstance(self._store, PgReportingLedgerStore)
            else ()
        )
        for attempt in range(3):
            leased = None
            with source_turn():
                try:
                    now = self._clock()
                    leased = await self._store.lease_period_close(
                        worker_id=self._worker_id, now=now, lease_seconds=self._lease_seconds
                    )
                    turn = WorkerTurn(leased=leased)
                    if leased is None:
                        return turn
                    await self._work_leased_configuration(leased, turn, now=now)
                except retryable:
                    # Store transactions have already exited/rolled back. A
                    # committed lease is released only under its original fence;
                    # a competitor may win before the next acquisition.
                    if leased is not None:
                        await self._release_worker_lease(leased, retryable)
                    if attempt == 2:
                        raise
                except BaseException as original:
                    if leased is not None:
                        try:
                            await self._release_worker_lease(leased, retryable)
                        except retryable:
                            # Never turn an immutable-content conflict or
                            # cancellation into a retryable cleanup failure.
                            raise original from None
                    raise
                else:
                    # Exhausted release retries escape from here, rather than
                    # starting another turn or reporting a false empty success.
                    await self._release_worker_lease(leased, retryable)
                    return turn
            await asyncio.sleep(0.05 * (2**attempt))
        raise AssertionError("unreachable worker retry")  # pragma: no cover

    async def _release_worker_lease(
        self, leased: LeasedConfiguration, retryable: tuple[type[Exception], ...]
    ) -> None:
        for attempt in range(3):
            try:
                await self._store.release_period_close(leased, worker_id=self._worker_id)
                return
            except retryable:
                if attempt == 2:
                    raise
            await asyncio.sleep(0.05 * (2**attempt))

    async def _work_leased_configuration(
        self, leased: LeasedConfiguration, turn: WorkerTurn, *, now: datetime
    ) -> None:
        from adcp.reporting.production.contracts import _SourceAuthorizationRevokedError

        try:
            configuration = next(
                (
                    candidate
                    for candidate in await self._store.list_configurations(
                        caller=ReportingDeliveryPrincipal(leased.account_id, leased.consumer_id),
                        delivery_config_ids=[leased.delivery_config_id],
                    )
                    if candidate.generation_key == leased.generation_key
                ),
                None,
            )
            if configuration is None or configuration.quarantined:
                return
            require_account_work(configuration.account_id)
            await self._close_elapsed_periods(configuration, turn, now=now)
            await self._acquire_pending(configuration, turn, now=now)
        except _SourceAuthorizationRevokedError:
            # Keep pending acquisitions retryable. Authorization may be
            # restored on the next turn; it is not a history failure.
            return

    async def run_configuration(
        self,
        configuration: ReportingConfiguration,
        *,
        now: datetime | None = None,
    ) -> WorkerTurn:
        """Run one turn for an already-routed configuration generation.

        High-level orchestrators use this entry point after freezing adapter,
        currency, and source scope for a specific generation. The orchestrator
        owns cross-process scheduling; obligation and revision writes remain
        convergent and immutable in the ledger store.

        Most adopters should continue using :meth:`run_worker`, whose store
        lease chooses a configuration automatically.
        """
        from adcp.reporting.production.contracts import _SourceAuthorizationRevokedError

        boundary = now or self._clock()
        turn = WorkerTurn()
        with source_turn():
            try:
                require_account_work(configuration.account_id)
                await self._close_elapsed_periods(configuration, turn, now=boundary)
                await self._acquire_pending(configuration, turn, now=boundary)
            except _SourceAuthorizationRevokedError:
                return turn
        return turn

    # -- step 1: obligations before reports ------------------------------

    async def close_elapsed_periods(
        self, configuration: ReportingConfiguration, *, now: datetime | None = None
    ) -> list[ReportingObligationRecord]:
        """Commit an obligation for every elapsed eligible period.

        Public because a seller often wants to run this on its own cadence --
        the obligation must land in the first ledger snapshot strictly after the
        period boundary, independent of whether the source is healthy.
        """
        if configuration.quarantined:
            raise LedgerConflictError(
                "REPORTING_GENERATION_QUARANTINED", "legacy generations cannot run"
            )
        turn = WorkerTurn()
        return await self._close_elapsed_periods(configuration, turn, now=now or self._clock())

    async def _close_elapsed_periods(
        self,
        configuration: ReportingConfiguration,
        turn: WorkerTurn,
        *,
        now: datetime,
    ) -> list[ReportingObligationRecord]:
        from adcp.reporting.ledger.producer_progress import ReportingProducerProgress

        progress = self._store if isinstance(self._store, ReportingProducerProgress) else None
        after = None if progress is None else await progress.producer_closed_through(configuration)
        committed: list[ReportingObligationRecord] = []
        for boundary in self._elapsed_periods(configuration, now=now, after=after):
            existing = await self._store.find_obligation(
                account_id=configuration.account_id,
                consumer_id=configuration.consumer_id,
                delivery_config_id=configuration.delivery_config_id,
                delivery_config_version=configuration.delivery_config_version,
                period_start=boundary.start,
                period_end=boundary.end,
            )
            if existing is not None:
                if progress is not None:
                    await progress.commit_producer_period(
                        configuration, existing, previous_end=after
                    )
                    after = boundary.end
                continue
            obligation = ReportingObligationRecord(
                reporting_obligation_id=self._obligation_id(configuration, boundary),
                account_id=configuration.account_id,
                consumer_id=configuration.consumer_id,
                delivery_config_id=configuration.delivery_config_id,
                delivery_config_version=configuration.delivery_config_version,
                report_definition_id=configuration.report_definition_id,
                reporting_profile=configuration.reporting_profile,
                feed_purpose=configuration.feed_purpose,
                period=boundary,
                # The denominator froze at the period boundary. Resolving it
                # "now" would silently include a media buy that started after
                # the period it is being reported against.
                scope_resolved_at=boundary.end,
                media_buy_ids=configuration.media_buy_ids,
                required_finality=configuration.required_finality,
                automated_recovery_deadline_at=(
                    boundary.expected_at + configuration.automated_recovery_window
                ),
                schedule=configuration.schedule,
                definition=configuration.definition,
                created_at=now,
            )
            resolved = self._currency_resolver(configuration, obligation)
            currency = await resolved if inspect.isawaitable(resolved) else resolved
            obligation = replace(obligation, currency=validate_currency(currency))
            stored = (
                await self._store.commit_obligation(obligation)
                if progress is None
                else await progress.commit_producer_period(
                    configuration, obligation, previous_end=after
                )
            )
            after = boundary.end
            committed.append(stored)
            turn.obligations_committed.append(stored.reporting_obligation_id)
        return committed

    def _elapsed_periods(
        self,
        configuration: ReportingConfiguration,
        *,
        now: datetime,
        after: datetime | None = None,
    ) -> list[ReportingPeriodBoundary]:
        """Every eligible period that has closed but is not yet obligated.

        A period is eligible once its *end* is at or before now. Activation
        owes the first full period; deactivation after a period has started
        retains that whole period and its original SLA. Polling forecasts use
        the same committed-generation iterator.
        """
        from itertools import islice

        from adcp.reporting.ledger.schedule import committed_periods

        boundaries: list[ReportingPeriodBoundary] = []
        near = (
            None
            if after is None
            else after + iso_duration_to_timedelta(configuration.schedule.delivery_sla)
        )
        periods = (
            period
            for period in committed_periods(configuration, near=near)
            if after is None or period.end > after
        )
        for boundary in islice(periods, self._max_periods_per_turn):
            if _utc(boundary.end) > _utc(now):
                break
            boundaries.append(boundary)
        return boundaries

    @staticmethod
    def _obligation_id(
        configuration: ReportingConfiguration, boundary: ReportingPeriodBoundary
    ) -> str:
        digest = hashlib.sha256(
            canonical_json_utf8_v1(
                [
                    configuration.account_id,
                    configuration.consumer_id,
                    configuration.delivery_config_id,
                    configuration.delivery_config_version,
                    configuration.report_definition_id,
                    _utc(boundary.start).isoformat(),
                    _utc(boundary.end).isoformat(),
                ]
            )
        ).hexdigest()
        return f"rpo_{digest[:40]}"

    # -- steps 2-4: acquire, commit, restate -----------------------------

    async def _acquire_pending(
        self, configuration: ReportingConfiguration, turn: WorkerTurn, *, now: datetime
    ) -> None:
        from adcp.reporting.ledger.producer_progress import ReportingProducerProgress

        if isinstance(self._store, ReportingProducerProgress):
            await self._acquire_progress(self._store, configuration, turn, now=now)
            return
        for boundary in self._elapsed_periods(configuration, now=now):
            obligation = await self._store.find_obligation(
                account_id=configuration.account_id,
                consumer_id=configuration.consumer_id,
                delivery_config_id=configuration.delivery_config_id,
                delivery_config_version=configuration.delivery_config_version,
                period_start=boundary.start,
                period_end=boundary.end,
            )
            if obligation is None:
                continue
            if _utc(now) < self._source_available_at(obligation):
                continue
            try:
                policy = self._settling_policy(configuration, obligation)
                if policy is None:
                    await self.acquire_obligation(configuration, obligation, turn=turn, now=now)
                else:
                    await self._acquire_with_settling_policy(
                        configuration,
                        obligation,
                        policy=policy,
                        turn=turn,
                        now=now,
                    )
            except (ReportingCurrencyError, LedgerConflictError) as error:
                if isinstance(error, LedgerConflictError) and error.code != "OBSERVATION_CONFLICT":
                    raise
                # Currency interpretation and competing observations are local
                # slice failures. Let later periods make progress; a later turn
                # can read the winning observation or retry the same reservation.
                logger.info(
                    "reporting slice failed obligation=%s code=%s",
                    obligation.reporting_obligation_id,
                    error.code,
                )
                turn.slices_failed.append(obligation.reporting_obligation_id)
                self._note_escalation(obligation, turn, now=now)

    async def _acquire_progress(
        self,
        progress: ReportingProducerProgress,
        configuration: ReportingConfiguration,
        turn: WorkerTurn,
        *,
        now: datetime,
    ) -> None:
        identifiers = await progress.next_producer_obligations(
            configuration, now=now, limit=self._max_periods_per_turn
        )
        for identifier in identifiers:
            obligation = await self._store.get_obligation(
                account_id=configuration.account_id, reporting_obligation_id=identifier
            )
            if obligation is None or obligation.generation_key != configuration.generation_key:
                raise LedgerConflictError("HISTORY_UNAVAILABLE", "producer history is unavailable")
            if _utc(now) < self._source_available_at(obligation):
                # Waiting for the declared source window is not completion.
                # Leave the durable work item available to a later turn.
                continue
            policy = self._settling_policy(configuration, obligation)
            finished = policy is None
            try:
                if policy is None:
                    await self.acquire_obligation(configuration, obligation, turn=turn, now=now)
                else:
                    finished = await self._acquire_with_settling_policy(
                        configuration, obligation, policy=policy, turn=turn, now=now
                    )
            except (ReportingCurrencyError, LedgerConflictError) as error:
                if isinstance(error, LedgerConflictError) and error.code not in {
                    "HISTORY_UNAVAILABLE",
                    "EMPTY_DENOMINATOR",
                    "OBSERVATION_CONFLICT",
                }:
                    raise
                turn.slices_failed.append(identifier)
                self._note_escalation(obligation, turn, now=now)
                # Retain the existing corrupt-history parking check. A transient
                # currency failure or observation conflict must not retire work.
                finished = error.code != "OBSERVATION_CONFLICT" and (
                    policy is None or isinstance(error, LedgerConflictError)
                )
            if finished:
                await progress.finish_producer_acquisition(
                    configuration, reporting_obligation_id=identifier
                )

    def _source_available_at(
        self,
        obligation: ReportingObligationRecord,
        *,
        target_finality: str | None = None,
        offering_id: str | None = None,
        worst_case: bool = False,
    ) -> datetime:
        end = _utc(obligation.period.end)
        offering_id = offering_id or self._offerings.offering_for(
            target_finality or obligation.required_finality
        )
        if offering_id is None:
            # The acquisition path reports the existing missing-offering error.
            return end
        offering = self._source.capabilities.offering(offering_id)
        lag = (
            offering.worst_case_availability_lag
            if worst_case
            else offering.expected_availability_lag
        )
        ready_at = end + timedelta(milliseconds=iso_duration_milliseconds_v1(lag))
        if isinstance(offering, AuthoritativeOfferingV1):
            zone = ZoneInfo(offering.source_timezone or obligation.period.source_timezone)
            local_day = end.astimezone(zone).date() + timedelta(days=offering.days_after_period_end)
            hour, minute = (int(part) for part in offering.source_local_ready_time.split(":"))
            local_ready = datetime.combine(local_day, datetime.min.time()).replace(
                hour=hour, minute=minute, tzinfo=zone
            )
            # Pick the later instant across a clock fold or gap. Ambiguous
            # local readiness must not make the scheduler read the source early.
            ready_at = max(
                ready_at,
                _utc(local_ready.replace(fold=0)),
                _utc(local_ready.replace(fold=1)),
            )
        return ready_at

    def _settling_policy(
        self,
        configuration: ReportingConfiguration,
        obligation: ReportingObligationRecord,
    ) -> _SettlingPolicy | None:
        """Resolve the source policy or SDK fallback for a snapshot obligation."""
        offering_id = self._offerings.snapshot_offering_id
        if obligation.required_finality != "snapshot" or offering_id is None:
            return None
        offering = self._source.capabilities.offering(offering_id)
        if not isinstance(offering, ProvisionalSnapshotOfferingV1):
            return None
        if offering.restatement_cadence is not None:
            cadence = timedelta(
                milliseconds=iso_duration_milliseconds_v1(offering.restatement_cadence)
            )
        else:
            cadence = max(
                iso_duration_to_timedelta(configuration.schedule.period_duration),
                timedelta(milliseconds=iso_duration_milliseconds_v1(offering.fastest_safe_cadence)),
            )
        close_lag = (
            timedelta(milliseconds=iso_duration_milliseconds_v1(offering.official_close_lag))
            if offering.official_close_lag is not None
            else None
        )
        return _SettlingPolicy(
            restatement_window=timedelta(
                milliseconds=iso_duration_milliseconds_v1(offering.restatement_window or "P3D")
            ),
            restatement_cadence=cadence,
            official_close_lag=close_lag,
        )

    async def _acquire_with_settling_policy(
        self,
        configuration: ReportingConfiguration,
        obligation: ReportingObligationRecord,
        *,
        policy: _SettlingPolicy,
        turn: WorkerTurn,
        now: datetime,
    ) -> bool:
        """Return whether policy-controlled acquisition may leave the pending queue.

        A readable snapshot completes one acquisition, not the settling policy.
        The progress store retains its rotating work item until the policy ends.
        """
        checkpoint_store = self._restatement_store()
        observation_store = self._observation_store()
        latest = await observation_store.get_provisional_observation(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        if latest is not None:
            frozen = latest.acquisition.policy
            policy = _SettlingPolicy(frozen.window, frozen.cadence, frozen.official_close_lag)
        explicit_close = (
            policy.official_close_lag is not None
            and self._offerings.official_offering_id is not None
        )
        revisions = await self._store.list_revisions(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        if any(item.finality == "official" for item in revisions):
            return True
        checkpoint = await checkpoint_store.get_restatement_checkpoint(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        ordinal = checkpoint.next_observation if checkpoint is not None else len(revisions)
        pending = await self._pending_acquisition(obligation, ordinal=ordinal)
        if pending is not None:
            # Finish frozen source work before selecting a new finality. A
            # snapshot retry may cross the official-close boundary while the
            # authoritative offering is still unavailable.
            request = pending.request()
            if _utc(now) < self._source_available_at(obligation, offering_id=request.offering_id):
                return False
            await self.acquire_obligation(
                configuration,
                obligation,
                restate=bool(revisions),
                turn=turn,
                now=now,
                target_finality=(
                    "snapshot"
                    if request.publication_class == "PROVISIONAL_SNAPSHOT"
                    else "official"
                ),
                track_settling=True,
            )
            revisions = await self._store.list_revisions(
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            if any(item.finality == "official" for item in revisions):
                return True
            if not explicit_close:
                published = await observation_store.get_provisional_observation(
                    account_id=obligation.account_id,
                    reporting_obligation_id=obligation.reporting_obligation_id,
                )
                return (
                    published is not None
                    and published.acquisition.ordinal == ordinal
                    and published.next_due_at is None
                )
            return False
        if not revisions:
            await self.acquire_obligation(
                configuration,
                obligation,
                turn=turn,
                now=now,
                target_finality="snapshot",
                track_settling=True,
            )
            return False

        declared_until = _utc(obligation.period.end) + policy.restatement_window
        settles_at = (
            _utc(checkpoint.provisional_until)
            if checkpoint is not None and checkpoint.provisional_until is not None
            else declared_until
        )
        last_checked = (
            checkpoint.checked_at
            if checkpoint is not None
            else max(revisions, key=lambda item: _utc(item.created_at)).created_at
        )
        next_due = (
            latest.next_due_at
            if latest is not None
            else min(_utc(last_checked) + policy.restatement_cadence, settles_at)
        )
        # Preserve explicitly configured official-close boundary precedence.
        # Snapshot-only policies retain their final inclusive due read through
        # downtime; expiry alone is never an official publication.
        if not explicit_close or _utc(now) < settles_at:
            if next_due is None:
                return not explicit_close
            if _utc(now) >= _utc(next_due):
                await self.acquire_obligation(
                    configuration,
                    obligation,
                    restate=True,
                    turn=turn,
                    now=now,
                    target_finality="snapshot",
                    track_settling=True,
                )
                published = await observation_store.get_provisional_observation(
                    account_id=obligation.account_id,
                    reporting_obligation_id=obligation.reporting_obligation_id,
                )
                if not explicit_close and published is not None and published.next_due_at is None:
                    return True
            return False

        if policy.official_close_lag is None or self._offerings.official_offering_id is None:
            return True
        closes_at = max(
            settles_at,
            _utc(obligation.period.end) + policy.official_close_lag,
            self._source_available_at(obligation, target_finality="official"),
        )
        if _utc(now) >= closes_at:
            await self.acquire_obligation(
                configuration,
                obligation,
                restate=True,
                turn=turn,
                now=now,
                target_finality="official",
                track_settling=True,
            )
            # An attempted close can still return not-ready. Retire only after
            # the authoritative revision was actually committed.
            revisions = await self._store.list_revisions(
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            return any(item.finality == "official" for item in revisions)
        return False

    async def acquire_obligation(
        self,
        configuration: ReportingConfiguration,
        obligation: ReportingObligationRecord,
        *,
        restate: bool = False,
        turn: WorkerTurn | None = None,
        now: datetime | None = None,
        target_finality: str | None = None,
        track_settling: bool = False,
        manual_replay: bool = False,
    ) -> ReportingRevisionRecord | None:
        """Drive one obligation from its source and commit what comes back.

        Returns the committed revision, or ``None`` when the source is not ready
        or the obligation is already satisfied.

        ``restate`` asks for a *new observation* of an already-satisfied
        snapshot obligation.  Without it a satisfied obligation is left alone,
        because re-reading a settled period on every worker turn would burn
        upstream quota to republish bytes nobody asked for.

        ``now`` freezes dispatch and the source read cutoff. Revision creation
        uses a fresh producer clock sample after the staged objects are read.
        """
        if configuration.generation_key != obligation.generation_key:
            raise LedgerConflictError(
                "CONFIGURATION_GENERATION_MISMATCH",
                "the source configuration must belong to the obligation's account and generation",
            )
        obligation = await self._stored_obligation(obligation)
        turn = turn or WorkerTurn()
        now = now or self._clock()
        finality = target_finality or obligation.required_finality
        if not manual_replay and await self._retry_not_before(obligation, turn, now=now):
            retry_offering_id: str | None = None
            if track_settling:
                checkpoint = await self._restatement_store().get_restatement_checkpoint(
                    account_id=obligation.account_id,
                    reporting_obligation_id=obligation.reporting_obligation_id,
                )
                ordinal = (
                    checkpoint.next_observation
                    if checkpoint is not None
                    else len(
                        await self._store.list_revisions(
                            account_id=obligation.account_id,
                            reporting_obligation_id=obligation.reporting_obligation_id,
                        )
                    )
                )
                pending = await self._pending_acquisition(obligation, ordinal=ordinal)
                if pending is not None:
                    retry_offering_id = pending.request().offering_id
            keys = turn._retry_keys_by_obligation[obligation.reporting_obligation_id]
            retryable = not any(
                turn._retry_entries[key].blocked for key in keys if key in turn._retry_entries
            )
            self._note_escalation(
                obligation,
                turn,
                now=now,
                availability_retry=retryable,
                target_finality=finality,
                offering_id=retry_offering_id,
            )
            return None
        revisions = await self._store.list_revisions(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        selection = select_reporting_revision(
            revisions,
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
            required_finality=obligation.required_finality,
        )
        if selection.kind == "corrupt":
            raise LedgerConflictError("HISTORY_UNAVAILABLE", "the revision history requires repair")
        current = selection.revision if selection.kind == "selected" else None
        if current is not None and current.finality == "official":
            # An official close is terminal. A later source correction is an
            # adjustment, never another acquisition.
            return None
        satisfied = current is not None and current.readable
        if satisfied and not restate:
            return None
        # Everything below needs the frozen code: the slice request carries it,
        # the manifest is checked against it, and the revision is written under
        # it. Gate here rather than earlier so a settled legacy obligation stays
        # the no-op it already was instead of becoming an error on every turn.
        require_frozen_currency(obligation.currency)

        offering_id = self._offerings.offering_for(finality)
        if offering_id is None:
            raise LedgerConflictError(
                "NO_OFFERING_FOR_FINALITY",
                f"this producer declares no source offering for {finality} reporting",
            )

        constituents = await self._admitted_source_constituents(configuration, obligation)

        checkpoint_store = self._restatement_store() if track_settling else None
        checkpoint = (
            await checkpoint_store.get_restatement_checkpoint(
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            if checkpoint_store is not None
            else None
        )
        observation = checkpoint.next_observation if checkpoint is not None else len(revisions)
        request = self._build_slice(
            configuration,
            obligation,
            offering_id,
            finality=finality,
            now=now,
            observation=observation,
            constituents=constituents,
            trigger="manual_replay" if manual_replay else "scheduled_poll",
        )
        acquisition = None
        if track_settling:
            policy = self._settling_policy(configuration, obligation)
            assert policy is not None
            latest = await self._observation_store().get_provisional_observation(
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            frozen_policy = (
                latest.acquisition.policy
                if latest is not None
                else ProvisionalPolicy(
                    policy.restatement_window, policy.restatement_cadence, policy.official_close_lag
                )
            )
            leaf = self._current_snapshot(revisions)
            acquisition = await self._observation_store().reserve_provisional_acquisition(
                ProvisionalAcquisition(
                    request.model_dump_json(),
                    observation,
                    frozen_policy,
                    leaf.reporting_revision_id if leaf is not None else None,
                    checkpoint.provisional_until if checkpoint is not None else None,
                )
            )
            request = acquisition.request(deadline_at=_utc(now) + self._offerings.slice_timeout)
            if manual_replay:
                request = request.model_copy(update={"trigger": "manual_replay"})
            finality = (
                "snapshot" if request.publication_class == "PROVISIONAL_SNAPSHOT" else "official"
            )
        cancel = asyncio.Event()
        execution = asyncio.create_task(
            self._execute_source(
                configuration, request, admitted_constituents=constituents, cancel=cancel
            )
        )
        try:
            result = await asyncio.wait_for(
                asyncio.shield(execution),
                timeout=self._offerings.slice_timeout.total_seconds(),
            )
        except asyncio.CancelledError:
            cancel.set()
            await cancel_and_settle(execution)
            raise
        except asyncio.TimeoutError:
            cancel.set()
            # wait_for's own cancellation join can be interrupted by a second
            # cancellation of this producer (notably on Python 3.10). Retain
            # the execution explicitly until even synchronous work has settled.
            await cancel_and_settle(execution)
            turn.slices_failed.append(obligation.reporting_obligation_id)
            await self._schedule_retry(obligation, turn, now=now, replayed=manual_replay)
            self._note_escalation(
                obligation,
                turn,
                now=now,
                availability_retry=True,
                offering_id=request.offering_id,
            )
            return None

        if isinstance(result, _InlineStorageFailure):
            from adcp.reporting.inline_storage import InlineStorageError

            # Return closed data from the executor task: a raw driver exception
            # must not survive through a context manager or Task wakeup frame.
            raise InlineStorageError(result.code)

        if not result.ok:
            error = result.error
            assert error is not None
            logger.info(
                "reporting slice failed obligation=%s code=%s retry=%s",
                obligation.reporting_obligation_id,
                error.code,
                error.retry,
            )
            turn.slices_failed.append(obligation.reporting_obligation_id)
            await self._schedule_retry(
                obligation,
                turn,
                now=now,
                scope=error.scope,
                retry_after_seconds=error.retry_after_seconds,
                blocked=error.retry == "terminal",
                replayed=manual_replay,
            )
            self._note_escalation(
                obligation,
                turn,
                now=now,
                availability_retry=error.retry == "retryable",
                offering_id=request.offering_id,
            )
            return None

        manifest = self._verified_manifest(result)
        if request.coverage.expected == "full" and manifest.coverage.status != "full":
            raise LedgerConflictError(
                "MANIFEST_MISMATCH",
                "a full-coverage request cannot complete with partial or missing coverage",
            )
        self._validate_manifest_currency(obligation, manifest)
        rows = await self._read_rows(request, manifest)
        # ``now`` freezes dispatch/lease/cutoff decisions, not publication.
        # A conforming source can observe finality while acquisition is running.
        # Every successful scheduled read, even unchanged content, reaches the
        # atomic revision/observation/checkpoint commit with this fresh anchor.
        published_at = self._clock()
        if _utc(published_at) < _utc(now):
            raise LedgerConflictError(
                "PUBLICATION_TIME_INVALID",
                "producer clock regressed during acquisition; correct the clock before retrying",
            )
        committed = await self.commit_revision_from_manifest(
            obligation,
            manifest,
            rows=rows,
            finality=finality,
            now=published_at,
            turn=turn,
            acquisition=acquisition,
        )
        await self._clear_retry(obligation, turn=turn, now=published_at)
        return committed

    def _retry_keys(self, obligation: ReportingObligationRecord) -> tuple[str, str, str]:
        source = hashlib.sha256(
            canonical_json_utf8_v1(
                [self._offerings.publication_namespace, self._offerings.source_scope]
            )
        ).hexdigest()
        return (
            f"slice:{obligation.account_id}:{obligation.reporting_obligation_id}",
            f"account:{obligation.account_id}:{source}",
            f"source:{source}",
        )

    async def _get_retry(self, key: str) -> RetryScheduleEntry | None:
        return await self._retry_store.get_retry_schedule(scope_key=key)

    async def _record_retry(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
        return await self._retry_store.record_retry_schedule(entry)

    @staticmethod
    def _refresh_retry(turn: WorkerTurn, *, now: datetime) -> None:
        candidates = []
        for keys in turn._retry_keys_by_obligation.values():
            entries = [turn._retry_entries[key] for key in keys if key in turn._retry_entries]
            if any(entry.blocked for entry in entries):
                continue
            due = [entry.retry_not_before for entry in entries if entry.attempt > 0]
            if due:
                candidates.append(max(due, key=_utc))
        turn.earliest_retry_at = min(candidates, key=_utc) if candidates else None

    async def _retry_not_before(
        self, obligation: ReportingObligationRecord, turn: WorkerTurn, *, now: datetime
    ) -> bool:
        keys = self._retry_keys(obligation)
        schedules = [await self._get_retry(key) for key in keys]
        deferred = [
            entry
            for entry in schedules
            if entry is not None
            and entry.attempt > 0
            and (entry.blocked or _utc(entry.retry_not_before) > _utc(now))
        ]
        if not deferred:
            return False
        turn._retry_keys_by_obligation[obligation.reporting_obligation_id] = keys
        turn._retry_entries.update(
            (entry.scope_key, entry) for entry in schedules if entry is not None
        )
        self._refresh_retry(turn, now=now)
        return True

    async def _schedule_retry(
        self,
        obligation: ReportingObligationRecord,
        turn: WorkerTurn,
        *,
        now: datetime,
        scope: str = "slice",
        retry_after_seconds: float | None = None,
        blocked: bool = False,
        replayed: bool = False,
    ) -> None:
        # Dispatch ``now`` can be synthetic in tests and integrations. A slow
        # source may finish long after it, so anchor the cooldown at completion.
        completed_at = max(_utc(now), _utc(self._clock()))
        keys = self._retry_keys(obligation)
        shared = keys[1 if scope == "account" else 2]
        affected = keys[:1] if scope == "slice" else ((shared,) if blocked else (keys[0], shared))
        turn._retry_keys_by_obligation[obligation.reporting_obligation_id] = keys
        for key in keys:
            current = await self._get_retry(key)
            if current is not None:
                turn._retry_entries[key] = current
        for key in affected:
            previous = await self._get_retry(key)
            attempt = (previous.attempt if previous is not None else 0) + 1
            # Stable per-scope jitter keeps retries spread across configurations
            # and gives identical decisions to workers sharing a store.
            jitter = (
                0.8
                + 0.4
                * int.from_bytes(hashlib.sha256(f"{key}:{attempt}".encode()).digest()[:2], "big")
                / 65535
            )
            exponential = self._retry_initial_delay * (2 ** min(attempt - 1, 30))
            delay = min(self._retry_max_delay, exponential * jitter)
            if retry_after_seconds is not None:
                try:
                    delay = max(delay, timedelta(seconds=retry_after_seconds))
                except OverflowError:
                    delay = timedelta.max
            if completed_at >= _utc(obligation.automated_recovery_deadline_at):
                delay = max(delay, self._post_deadline_retry_interval)
            try:
                retry_at = completed_at + delay
            except OverflowError:
                retry_at = datetime.max.replace(tzinfo=timezone.utc)
            stored = await self._record_retry(
                RetryScheduleEntry(
                    key,
                    completed_at if blocked else retry_at,
                    attempt,
                    blocked,
                    recorded_at=completed_at,
                    replayed=replayed,
                )
            )
            turn._retry_entries[key] = stored
        if replayed:
            # A manual probe supersedes an earlier terminal blast radius. The
            # new error's declared scope governs subsequent automatic work.
            for key in keys:
                if key in affected:
                    continue
                prior = await self._get_retry(key)
                if prior is not None and prior.blocked:
                    turn._retry_entries[key] = await self._record_retry(
                        RetryScheduleEntry(
                            key, completed_at, 0, recorded_at=completed_at, replayed=True
                        )
                    )
        self._refresh_retry(turn, now=completed_at)

    async def _clear_retry(
        self, obligation: ReportingObligationRecord, *, turn: WorkerTurn, now: datetime
    ) -> None:
        for key in self._retry_keys(obligation):
            prior = await self._get_retry(key)
            if prior is not None and prior.attempt:
                stored = await self._record_retry(
                    RetryScheduleEntry(key, _utc(now), 0, recorded_at=_utc(now))
                )
                turn._retry_entries[key] = stored
        turn._retry_keys_by_obligation.pop(obligation.reporting_obligation_id, None)
        self._refresh_retry(turn, now=now)

    async def _admitted_source_constituents(
        self, configuration: ReportingConfiguration, obligation: ReportingObligationRecord
    ) -> tuple[ReportingConstituent, ...] | None:
        from adcp.reporting.ledger.producer_progress import ReportingProducerProgress
        from adcp.reporting.production.contracts import _SourceAuthorizationRevokedError

        if not isinstance(self._store, ReportingProducerProgress):
            return None
        try:
            return await self._store.producer_constituents(configuration, obligation)
        except _SourceAuthorizationRevokedError:
            source_revoked(configuration.account_id)

    def _check_source_authorization(
        self,
        configuration: ReportingConfiguration,
        offering_id: str,
        admitted_constituents: tuple[ReportingConstituent, ...] | None,
    ) -> None:
        from adcp.reporting.materializer.contracts import failure
        from adcp.reporting.production.contracts import (
            ReportingProductionSource,
            ReportingProductionSourceBinding,
            _SourceAuthorizationRevokedError,
        )

        require_account_work(configuration.account_id)
        if isinstance(self._source, ReportingProductionSource):
            try:
                binding = self._source.configuration_binding(configuration)
                if binding is None:
                    source_revoked(configuration.account_id)
                if type(binding) is not ReportingProductionSourceBinding:
                    raise failure("BINDING_MISMATCH")
                binding.check(configuration, self._source.capabilities, offering_id)
                if (
                    admitted_constituents is not None
                    and binding.constituents() != admitted_constituents
                ):
                    raise failure("BINDING_MISMATCH")
            except _SourceAuthorizationRevokedError:
                raise
            except Exception:
                raise failure("BINDING_MISMATCH") from None

    @asynccontextmanager
    async def _source_publication(
        self,
        configuration: ReportingConfiguration,
        offering_id: str,
        *,
        admitted_constituents: tuple[ReportingConstituent, ...] | None,
        seals: ReportingSealStore | None = None,
    ) -> AsyncIterator[InlineSealPublisher | None]:
        from adcp.reporting.production.contracts import ReportingProductionSource

        if not isinstance(self._source, ReportingProductionSource):
            yield None
            return
        publication = getattr(self._store, "_source_publication", None)
        if publication is None:
            raise TypeError("production source publication requires an SDK account lock")
        async with publication(configuration.account_id, seals=seals) as publish_seal:
            self._check_source_authorization(configuration, offering_id, admitted_constituents)
            yield publish_seal

    async def _execute_source(
        self,
        configuration: ReportingConfiguration,
        request: ReportingSourceSliceRequestV1,
        *,
        admitted_constituents: tuple[ReportingConstituent, ...] | None,
        cancel: asyncio.Event,
    ) -> ReportingSourceExecutorResult | _InlineStorageFailure:
        from adcp.reporting.inline_storage import InlineStorageError

        try:
            with bind_inline_publication(
                configuration.account_id,
                lambda seals: self._source_publication(
                    configuration,
                    request.offering_id,
                    admitted_constituents=admitted_constituents,
                    seals=seals,
                ),
            ):
                # Check inside the executing task: scheduling it is not dispatch.
                # Revocation never cancels a fetch that has already started.
                self._check_source_authorization(
                    configuration, request.offering_id, admitted_constituents
                )
                return await self._source.execute(request, cancel=cancel)
        except InlineStorageError as error:
            return _InlineStorageFailure(error.code)

    @asynccontextmanager
    async def _revision_publication(
        self, obligation: ReportingObligationRecord, offering_id: str
    ) -> AsyncIterator[None]:
        from adcp.reporting.production.contracts import ReportingProductionSource

        if not isinstance(self._source, ReportingProductionSource):
            yield
            return
        configuration = next(
            (
                candidate
                for candidate in await self._store.list_configurations(
                    caller=ReportingDeliveryPrincipal(
                        obligation.account_id, obligation.consumer_id
                    ),
                    delivery_config_ids=[obligation.delivery_config_id],
                )
                if candidate.generation_key == obligation.generation_key
            ),
            None,
        )
        if configuration is None:
            raise LedgerConflictError("HISTORY_UNAVAILABLE", "source generation is unavailable")
        # Read immutable admission scope before taking the publication lock. It
        # is not an authorization grant: the callback is checked again inside
        # the lock against this exact mapping, including on replay.
        constituents = await self._admitted_source_constituents(configuration, obligation)
        async with self._source_publication(
            configuration, offering_id, admitted_constituents=constituents
        ):
            yield

    def _restatement_store(self) -> RestatementCheckpointStore:
        explicit = all(
            inspect.getattr_static(self._store, name, None) is not None
            for name in ("get_restatement_checkpoint", "record_restatement_checkpoint")
        )
        if not explicit or not isinstance(self._store, RestatementCheckpointStore):
            raise LedgerConflictError(
                "RESTATEMENT_CHECKPOINTS_NOT_SUPPORTED",
                "a source settling window requires explicitly implemented durable "
                "restatement checkpoint methods",
            )
        return self._store

    def _observation_store(self) -> ProvisionalObservationStore:
        explicit = all(
            inspect.getattr_static(self._store, name, None) is not None
            for name in (
                "reserve_provisional_acquisition",
                "get_provisional_observation",
                "commit_provisional_observation",
            )
        )
        if not explicit or not isinstance(self._store, ProvisionalObservationStore):
            raise LedgerConflictError(
                "PROVISIONAL_OBSERVATIONS_NOT_SUPPORTED",
                "scheduled provisional reads require explicitly implemented atomic "
                "observation methods, including any decorating publisher's preparation",
            )
        return self._store

    async def _pending_acquisition(
        self, obligation: ReportingObligationRecord, *, ordinal: int
    ) -> ProvisionalAcquisition | None:
        if inspect.getattr_static(
            self._store, "get_provisional_acquisition", None
        ) is None or not isinstance(self._store, PendingProvisionalAcquisitionStore):
            return None
        return await self._store.get_provisional_acquisition(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
            ordinal=ordinal,
        )

    def _note_escalation(
        self,
        obligation: ReportingObligationRecord,
        turn: WorkerTurn,
        *,
        now: datetime,
        availability_retry: bool = False,
        target_finality: str | None = None,
        offering_id: str | None = None,
    ) -> None:
        """Record that this obligation has run out of automated recovery.

        The health projection derives ``delayed`` versus ``action_required``
        from the clock, so the worker does not set a state -- it only surfaces
        that the boundary has passed so a supervisor can alert instead of
        letting a dead feed idle inside a retry loop forever.
        """
        if availability_retry and _utc(now) < self._source_available_at(
            obligation,
            target_finality=target_finality,
            offering_id=offering_id,
            worst_case=True,
        ):
            return
        if _utc(now) >= _utc(obligation.automated_recovery_deadline_at):
            turn.escalated.append(obligation.reporting_obligation_id)

    def _verified_manifest(self, result: ReportingSourceExecutorResult) -> SourceBatchManifestV1:
        response = result.response
        manifest_bytes = result.manifest_bytes
        assert response is not None and manifest_bytes is not None
        return parse_verified_source_batch_manifest_v1(response.manifest, manifest_bytes)

    async def _read_rows(
        self, request: ReportingSourceSliceRequestV1, manifest: SourceBatchManifestV1
    ) -> list[dict[str, Any]]:
        """Materialize the manifest's staged rows into the revision's frozen rows."""
        if self._object_reader is None:
            return []
        import json

        rows: list[dict[str, Any]] = []
        cancel = asyncio.Event()
        for staged in manifest.objects:
            payload = await self._object_reader.read(
                object_ref=staged.object_ref,
                object_generation=staged.object_generation,
                account_id=request.identity.account_id,
                source_scope=request.identity.source_scope,
                cancel=cancel,
            )
            if hashlib.sha256(payload).hexdigest() != staged.sha256:
                raise LedgerConflictError(
                    "STAGED_OBJECT_MISMATCH",
                    f"staged object {staged.ordinal} does not match its manifest digest",
                )
            for line in payload.decode("utf-8").splitlines():
                if line.strip():
                    rows.append(json.loads(line))
        return rows

    async def commit_revision_from_manifest(
        self,
        obligation: ReportingObligationRecord,
        manifest: SourceBatchManifestV1,
        *,
        rows: Sequence[dict[str, Any]],
        finality: str,
        now: datetime | None = None,
        turn: WorkerTurn | None = None,
        acquisition: ProvisionalAcquisition | None = None,
    ) -> ReportingRevisionRecord:
        """Project a verified manifest into an immutable ledger revision.

        A snapshot restatement supersedes the current snapshot leaf; there is no
        edit path.  An official close is terminal, so a later source correction
        must arrive as an adjustment instead. ``now`` is a trusted publication
        instant, unlike the dispatch instant accepted by ``acquire_obligation``.
        Replaying a publication retains its original creation time and parent.
        """
        obligation = await self._stored_obligation(obligation)
        self._validate_manifest_currency(obligation, manifest)
        if any(cell.status == "unsupported" for cell in manifest.metric_availability):
            try:
                offering = self._source.capabilities.offering(manifest.offering_id)
                _validate_metric_applicability(offering.metrics, manifest.metric_availability)
            except (KeyError, ValueError):
                raise LedgerConflictError(
                    "MANIFEST_MISMATCH",
                    "unsupported cells require partial metric support with a reason",
                ) from None
        now = now or self._clock()
        turn = turn or WorkerTurn()
        existing = await self._store.list_revisions(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        selection = select_reporting_revision(
            existing,
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
            required_finality="snapshot",
        )
        # A retained official close coexists with the snapshot chain and wins
        # whole-history selection outright, so it is never the snapshot leaf.
        # Reading it as one would root a restatement at ``None`` and split the
        # obligation into two snapshot roots -- a permanently corrupt history
        # over immutable rows, with no repair path.
        leaf = select_reporting_revision(
            tuple(item for item in existing if item.finality == "snapshot"),
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
            required_finality="snapshot",
        )
        if selection.kind == "corrupt" or leaf.kind == "corrupt":
            raise LedgerConflictError("HISTORY_UNAVAILABLE", "the revision history requires repair")
        supersedes = (
            leaf.revision.reporting_revision_id
            if finality == "snapshot" and leaf.kind == "selected"
            else None
        )

        if acquisition is not None and finality == "snapshot":
            supersedes = acquisition.predecessor_revision_id

        control_totals = tuple((total.name, total.value) for total in manifest.control_totals)
        revision_id = f"rpr_{manifest.publication_id[4:44]}"
        prior = next((item for item in existing if item.reporting_revision_id == revision_id), None)
        created_at = prior.created_at if prior is not None else now
        if prior is not None:
            # Still reconstruct and verify the supplied content below. Merely
            # finding the ID must not bypass immutable-content validation.
            # On replay this retained parent wins over the acquisition's
            # predecessor, just as the retained creation time wins over now.
            supersedes = prior.supersedes_reporting_revision_id
        if (
            _utc(manifest.acquired_at) > _utc(now)
            or _utc(manifest.observed_at) > _utc(created_at)
            or _utc(manifest.finality_evidence.observed_at) > _utc(created_at)
            or (
                finality == "official"
                and _utc(manifest.finality_evidence.observed_at) < _utc(obligation.period.end)
            )
        ):
            raise LedgerConflictError(
                "PUBLICATION_TIME_INVALID",
                "source observation or finality is outside the publication time bounds; "
                "check source evidence and the producer clock before retrying",
            )
        revision = ReportingRevisionRecord(
            reporting_revision_id=revision_id,
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
            finality="official" if finality == "official" else "snapshot",
            revision_content_sha256=revision_content_sha256(
                reporting_revision_id=revision_id,
                row_count=manifest.row_count,
                control_totals=control_totals,
                reporting_rows=rows,
            ),
            row_count=manifest.row_count,
            control_totals=control_totals,
            observed_at=manifest.observed_at,
            data_through=manifest.data_through,
            created_at=created_at,
            supersedes_reporting_revision_id=supersedes,
            finality_basis=(
                (
                    "stabilized"
                    if manifest.finality_evidence.basis == "elapsed_settlement_window"
                    else "source_final"
                )
                if finality == "official"
                else None
            ),
            finality_policy_id=(
                f"{obligation.report_definition_id}:{manifest.offering_id}"
                if finality == "official"
                else None
            ),
            finalized_at=manifest.finality_evidence.observed_at if finality == "official" else None,
            source_publication_id=manifest.publication_id,
            source_manifest_sha256=manifest.content_fingerprint.split(":", 1)[-1],
        )
        async with self._revision_publication(obligation, manifest.offering_id):
            if self._revision_verifier is not None:
                from adcp.reporting.materializer.publication import verified_publication

                revision = verified_publication(self._revision_verifier, obligation, revision, rows)
            if acquisition is None:
                committed = await self._store.commit_revision(revision, rows)
            else:
                # ``now`` is the publication anchor, sampled after row acquisition.
                # The bounds check above already rejects acquired_at after now.
                checked_at = _utc(now)
                boundary = manifest.finality_evidence.provisional_until or (
                    _utc(obligation.period.end) + acquisition.policy.window
                )
                next_due = (
                    min(checked_at + acquisition.policy.cadence, _utc(boundary))
                    if finality == "snapshot" and checked_at < _utc(boundary)
                    else None
                )
                committed = await self._observation_store().commit_provisional_observation(
                    ProvisionalObservation(
                        acquisition,
                        revision_id,
                        checked_at,
                        _utc(boundary),
                        next_due,
                        manifest.model_dump_json(),
                    ),
                    revision,
                    rows,
                )
        turn.revisions_committed.append(committed.reporting_revision_id)
        return committed

    async def _stored_obligation(
        self, obligation: ReportingObligationRecord
    ) -> ReportingObligationRecord:
        """Always use the durable winner, including for public low-level calls."""
        stored = await self._store.get_obligation(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        if stored is None:
            raise LedgerConflictError(
                "OBLIGATION_NOT_FOUND", "commit the obligation before source work"
            )
        if stored.generation_key != obligation.generation_key:
            raise LedgerConflictError(
                "CONFIGURATION_GENERATION_MISMATCH", "the obligation's retained generation differs"
            )
        return stored

    @staticmethod
    def _validate_manifest_currency(
        obligation: ReportingObligationRecord, manifest: SourceBatchManifestV1
    ) -> None:
        currency = require_frozen_currency(obligation.currency)
        if manifest.currency != currency:
            raise ReportingCurrencyError(
                "CURRENCY_MISMATCH", "source manifest currency disagrees with the frozen obligation"
            )
        identity = manifest.identity
        if (
            identity.account_id != obligation.account_id
            or identity.reporting_obligation_id != obligation.reporting_obligation_id
            or identity.delivery_config_id != obligation.delivery_config_id
            or identity.delivery_config_version != obligation.delivery_config_version
            or identity.report_definition_id != obligation.report_definition_id
        ):
            raise LedgerConflictError("MANIFEST_MISMATCH", "manifest does not bind this obligation")
        definition = obligation.definition
        units = {"spend": currency}
        ReportingProducer._validate_definition_binding(obligation, manifest.contract)
        if definition is not None:
            units.update(definition.monetary_metric_units)
            units.update(definition.monetary_control_total_units)
        for total in manifest.control_totals:
            expected = units.get(total.name)
            if expected is not None and total.unit is not None and total.unit != expected:
                raise ReportingCurrencyError(
                    "CURRENCY_MISMATCH",
                    "source control total unit disagrees with the frozen currency",
                )

    @staticmethod
    def _validate_definition_binding(
        obligation: ReportingObligationRecord, contract: ReportingContractIdentityV1
    ) -> None:
        definition = obligation.definition
        if definition is not None and (
            contract.report_definition_id != obligation.report_definition_id
            or contract.reporting_profile != obligation.reporting_profile
            or any(getattr(contract, name) != value for name, value in definition.to_wire().items())
        ):
            raise LedgerConflictError(
                "REPORT_DEFINITION_MISMATCH",
                "the source definition differs from the obligation's pin",
            )

    @staticmethod
    def _current_snapshot_leaf(revisions: Sequence[ReportingRevisionRecord]) -> str | None:
        current = ReportingProducer._current_snapshot(revisions)
        return current.reporting_revision_id if current is not None else None

    @staticmethod
    def _current_snapshot(
        revisions: Sequence[ReportingRevisionRecord],
    ) -> ReportingRevisionRecord | None:
        snapshots = [item for item in revisions if item.finality == "snapshot"]
        if not snapshots:
            return None
        superseded = {
            item.supersedes_reporting_revision_id
            for item in snapshots
            if item.supersedes_reporting_revision_id
        }
        leaves = [item for item in snapshots if item.reporting_revision_id not in superseded]
        if not leaves:
            return None
        return max(leaves, key=lambda item: (_utc(item.created_at), item.reporting_revision_id))

    # -- slice construction ----------------------------------------------

    def _build_slice(
        self,
        configuration: ReportingConfiguration,
        obligation: ReportingObligationRecord,
        offering_id: str,
        *,
        finality: str | None = None,
        now: datetime,
        observation: int = 0,
        constituents: tuple[ReportingConstituent, ...] | None = None,
        trigger: ReportingTriggerKind = "scheduled_poll",
    ) -> ReportingSourceSliceRequestV1:
        """Freeze one slice request from the obligation.

        ``source_execution_key`` is derived from the obligation, the offering,
        and the **observation ordinal**. Without a settling policy the ordinal
        is the number of revisions already committed. With one it comes from a
        durable checkpoint that also advances after an unchanged source read.

        That last term is what makes both behaviors correct at once. A *retry*
        of a failed acquisition commits nothing, so the ordinal is unchanged,
        the key is unchanged, and the source replays its sealed publication
        rather than minting a second one. A *restatement* follows a committed
        revision (or a successful unchanged check), so the ordinal advances
        and the source is genuinely re-read as a new immutable observation.
        The ordinal comes from durable state, not from the clock, so neither
        behavior depends on wall time.
        """
        offering = self._source.capabilities.offering(offering_id)
        resolved_constituents: list[ReportingConstituent] = (
            list(constituents)
            if constituents is not None
            else [
                MediaBuyConstituentV1(
                    constituent_id=media_buy_id,
                    product_id=obligation.report_definition_id,
                    media_buy_id=media_buy_id,
                )
                for media_buy_id in obligation.media_buy_ids
            ]
        )
        if not resolved_constituents:
            raise LedgerConflictError(
                "EMPTY_DENOMINATOR",
                "an obligation with no media buys has no source work; it is a platform-owned "
                "no-op, not a slice",
            )
        source_execution_key = (
            "rse-"
            + hashlib.sha256(
                canonical_json_utf8_v1(
                    [obligation.reporting_obligation_id, offering_id, observation]
                )
            ).hexdigest()[:40]
        )
        resolved_finality = finality or obligation.required_finality
        publication_class: ReportingPublicationClass = (
            "AUTHORITATIVE" if resolved_finality == "official" else "PROVISIONAL_SNAPSHOT"
        )
        return ReportingSourceSliceRequestV1(
            identity=ReportingSourceIdentityV1(
                account_id=obligation.account_id,
                delivery_config_id=obligation.delivery_config_id,
                delivery_config_version=obligation.delivery_config_version,
                report_definition_id=obligation.report_definition_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
                period_key=obligation.period.period_key,
                source_execution_key=source_execution_key,
                run_id=f"run-{_utc(now).strftime('%Y%m%dT%H%M%S')}",
                logical_slice_fingerprint=_logical_slice_fingerprint(obligation, offering_id),
                source_scope=dict(self._offerings.source_scope),
            ),
            adapter_build=self._source.capabilities.adapter_build,
            offering_id=offering_id,
            publication_namespace=self._offerings.publication_namespace,
            publication_class=publication_class,
            contract=offering.contract,
            period=ReportingSourcePeriodV1(
                period_key=obligation.period.period_key,
                source_local_date=_source_local_date(
                    obligation.period.start, obligation.period.source_timezone
                ),
                start=obligation.period.start,
                end=obligation.period.end,
                source_timezone=obligation.period.source_timezone,
                # The read cutoff is "as late as the source could possibly know
                # about", floored at the period end: a closed period is always
                # readable through its own boundary, and reading past *now*
                # would ask the source about a future it cannot answer for.
                source_read_cutoff_at=max(_utc(now), _utc(obligation.period.end)),
                grain=self._source.capabilities.offering(offering_id).grain,
                windowing=self._source.capabilities.offering(offering_id).windowing,
            ),
            revision_kind="authoritative" if publication_class == "AUTHORITATIVE" else "snapshot",
            trigger=trigger,
            coverage=ReportingSourceCoverageRequestV1(
                expected="full",
                constituents=resolved_constituents,
                denominator_fingerprint=coverage_denominator_fingerprint_v1(resolved_constituents),
            ),
            requested_metrics=list(self._offerings.requested_metrics),
            requested_dimensions=list(self._offerings.requested_dimensions),
            currency=require_frozen_currency(obligation.currency),
            deadline_at=_utc(now) + self._offerings.slice_timeout,
        )

Closes periods, drives the source, and commits immutable revisions.

Instance variables

prop escalation : ReportingDeliveryEscalation
Expand source code
@property
def escalation(self) -> ReportingDeliveryEscalation:
    """The advertised escalation commitment, for the status handler.

    Pass the same object to :class:`~adcp.reporting.ledger.status.ReportingStatusHandler`
    so the projection honours exactly the window the seller published. A
    handler with a different window than the capability block would escalate
    on a clock no buyer can see.
    """
    return self._escalation

The advertised escalation commitment, for the status handler.

Pass the same object to :class:~adcp.reporting.ledger.status.ReportingStatusHandler so the projection honours exactly the window the seller published. A handler with a different window than the capability block would escalate on a clock no buyer can see.

prop store : ReportingLedgerStore
Expand source code
@property
def store(self) -> ReportingLedgerStore:
    return self._store

Methods

async def acquire_obligation(self,
configuration: ReportingConfiguration,
obligation: ReportingObligationRecord,
*,
restate: bool = False,
turn: WorkerTurn | None = None,
now: datetime | None = None,
target_finality: str | None = None,
track_settling: bool = False,
manual_replay: bool = False) ‑> ReportingRevisionRecord | None
Expand source code
async def acquire_obligation(
    self,
    configuration: ReportingConfiguration,
    obligation: ReportingObligationRecord,
    *,
    restate: bool = False,
    turn: WorkerTurn | None = None,
    now: datetime | None = None,
    target_finality: str | None = None,
    track_settling: bool = False,
    manual_replay: bool = False,
) -> ReportingRevisionRecord | None:
    """Drive one obligation from its source and commit what comes back.

    Returns the committed revision, or ``None`` when the source is not ready
    or the obligation is already satisfied.

    ``restate`` asks for a *new observation* of an already-satisfied
    snapshot obligation.  Without it a satisfied obligation is left alone,
    because re-reading a settled period on every worker turn would burn
    upstream quota to republish bytes nobody asked for.

    ``now`` freezes dispatch and the source read cutoff. Revision creation
    uses a fresh producer clock sample after the staged objects are read.
    """
    if configuration.generation_key != obligation.generation_key:
        raise LedgerConflictError(
            "CONFIGURATION_GENERATION_MISMATCH",
            "the source configuration must belong to the obligation's account and generation",
        )
    obligation = await self._stored_obligation(obligation)
    turn = turn or WorkerTurn()
    now = now or self._clock()
    finality = target_finality or obligation.required_finality
    if not manual_replay and await self._retry_not_before(obligation, turn, now=now):
        retry_offering_id: str | None = None
        if track_settling:
            checkpoint = await self._restatement_store().get_restatement_checkpoint(
                account_id=obligation.account_id,
                reporting_obligation_id=obligation.reporting_obligation_id,
            )
            ordinal = (
                checkpoint.next_observation
                if checkpoint is not None
                else len(
                    await self._store.list_revisions(
                        account_id=obligation.account_id,
                        reporting_obligation_id=obligation.reporting_obligation_id,
                    )
                )
            )
            pending = await self._pending_acquisition(obligation, ordinal=ordinal)
            if pending is not None:
                retry_offering_id = pending.request().offering_id
        keys = turn._retry_keys_by_obligation[obligation.reporting_obligation_id]
        retryable = not any(
            turn._retry_entries[key].blocked for key in keys if key in turn._retry_entries
        )
        self._note_escalation(
            obligation,
            turn,
            now=now,
            availability_retry=retryable,
            target_finality=finality,
            offering_id=retry_offering_id,
        )
        return None
    revisions = await self._store.list_revisions(
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
    )
    selection = select_reporting_revision(
        revisions,
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
        required_finality=obligation.required_finality,
    )
    if selection.kind == "corrupt":
        raise LedgerConflictError("HISTORY_UNAVAILABLE", "the revision history requires repair")
    current = selection.revision if selection.kind == "selected" else None
    if current is not None and current.finality == "official":
        # An official close is terminal. A later source correction is an
        # adjustment, never another acquisition.
        return None
    satisfied = current is not None and current.readable
    if satisfied and not restate:
        return None
    # Everything below needs the frozen code: the slice request carries it,
    # the manifest is checked against it, and the revision is written under
    # it. Gate here rather than earlier so a settled legacy obligation stays
    # the no-op it already was instead of becoming an error on every turn.
    require_frozen_currency(obligation.currency)

    offering_id = self._offerings.offering_for(finality)
    if offering_id is None:
        raise LedgerConflictError(
            "NO_OFFERING_FOR_FINALITY",
            f"this producer declares no source offering for {finality} reporting",
        )

    constituents = await self._admitted_source_constituents(configuration, obligation)

    checkpoint_store = self._restatement_store() if track_settling else None
    checkpoint = (
        await checkpoint_store.get_restatement_checkpoint(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        if checkpoint_store is not None
        else None
    )
    observation = checkpoint.next_observation if checkpoint is not None else len(revisions)
    request = self._build_slice(
        configuration,
        obligation,
        offering_id,
        finality=finality,
        now=now,
        observation=observation,
        constituents=constituents,
        trigger="manual_replay" if manual_replay else "scheduled_poll",
    )
    acquisition = None
    if track_settling:
        policy = self._settling_policy(configuration, obligation)
        assert policy is not None
        latest = await self._observation_store().get_provisional_observation(
            account_id=obligation.account_id,
            reporting_obligation_id=obligation.reporting_obligation_id,
        )
        frozen_policy = (
            latest.acquisition.policy
            if latest is not None
            else ProvisionalPolicy(
                policy.restatement_window, policy.restatement_cadence, policy.official_close_lag
            )
        )
        leaf = self._current_snapshot(revisions)
        acquisition = await self._observation_store().reserve_provisional_acquisition(
            ProvisionalAcquisition(
                request.model_dump_json(),
                observation,
                frozen_policy,
                leaf.reporting_revision_id if leaf is not None else None,
                checkpoint.provisional_until if checkpoint is not None else None,
            )
        )
        request = acquisition.request(deadline_at=_utc(now) + self._offerings.slice_timeout)
        if manual_replay:
            request = request.model_copy(update={"trigger": "manual_replay"})
        finality = (
            "snapshot" if request.publication_class == "PROVISIONAL_SNAPSHOT" else "official"
        )
    cancel = asyncio.Event()
    execution = asyncio.create_task(
        self._execute_source(
            configuration, request, admitted_constituents=constituents, cancel=cancel
        )
    )
    try:
        result = await asyncio.wait_for(
            asyncio.shield(execution),
            timeout=self._offerings.slice_timeout.total_seconds(),
        )
    except asyncio.CancelledError:
        cancel.set()
        await cancel_and_settle(execution)
        raise
    except asyncio.TimeoutError:
        cancel.set()
        # wait_for's own cancellation join can be interrupted by a second
        # cancellation of this producer (notably on Python 3.10). Retain
        # the execution explicitly until even synchronous work has settled.
        await cancel_and_settle(execution)
        turn.slices_failed.append(obligation.reporting_obligation_id)
        await self._schedule_retry(obligation, turn, now=now, replayed=manual_replay)
        self._note_escalation(
            obligation,
            turn,
            now=now,
            availability_retry=True,
            offering_id=request.offering_id,
        )
        return None

    if isinstance(result, _InlineStorageFailure):
        from adcp.reporting.inline_storage import InlineStorageError

        # Return closed data from the executor task: a raw driver exception
        # must not survive through a context manager or Task wakeup frame.
        raise InlineStorageError(result.code)

    if not result.ok:
        error = result.error
        assert error is not None
        logger.info(
            "reporting slice failed obligation=%s code=%s retry=%s",
            obligation.reporting_obligation_id,
            error.code,
            error.retry,
        )
        turn.slices_failed.append(obligation.reporting_obligation_id)
        await self._schedule_retry(
            obligation,
            turn,
            now=now,
            scope=error.scope,
            retry_after_seconds=error.retry_after_seconds,
            blocked=error.retry == "terminal",
            replayed=manual_replay,
        )
        self._note_escalation(
            obligation,
            turn,
            now=now,
            availability_retry=error.retry == "retryable",
            offering_id=request.offering_id,
        )
        return None

    manifest = self._verified_manifest(result)
    if request.coverage.expected == "full" and manifest.coverage.status != "full":
        raise LedgerConflictError(
            "MANIFEST_MISMATCH",
            "a full-coverage request cannot complete with partial or missing coverage",
        )
    self._validate_manifest_currency(obligation, manifest)
    rows = await self._read_rows(request, manifest)
    # ``now`` freezes dispatch/lease/cutoff decisions, not publication.
    # A conforming source can observe finality while acquisition is running.
    # Every successful scheduled read, even unchanged content, reaches the
    # atomic revision/observation/checkpoint commit with this fresh anchor.
    published_at = self._clock()
    if _utc(published_at) < _utc(now):
        raise LedgerConflictError(
            "PUBLICATION_TIME_INVALID",
            "producer clock regressed during acquisition; correct the clock before retrying",
        )
    committed = await self.commit_revision_from_manifest(
        obligation,
        manifest,
        rows=rows,
        finality=finality,
        now=published_at,
        turn=turn,
        acquisition=acquisition,
    )
    await self._clear_retry(obligation, turn=turn, now=published_at)
    return committed

Drive one obligation from its source and commit what comes back.

Returns the committed revision, or None when the source is not ready or the obligation is already satisfied.

restate asks for a new observation of an already-satisfied snapshot obligation. Without it a satisfied obligation is left alone, because re-reading a settled period on every worker turn would burn upstream quota to republish bytes nobody asked for.

now freezes dispatch and the source read cutoff. Revision creation uses a fresh producer clock sample after the staged objects are read.

def advertised_reporting_delivery(self,
*,
consumer_status_task: bool,
offerings: Sequence[Mapping[str, Any]],
automated_recovery_window: timedelta,
status_retention_days: int,
extra: Mapping[str, Any] | None = None) ‑> dict[str, typing.Any]
Expand source code
def advertised_reporting_delivery(
    self,
    *,
    consumer_status_task: bool,
    offerings: Sequence[Mapping[str, Any]],
    automated_recovery_window: timedelta,
    status_retention_days: int,
    extra: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
    """The complete ``media_buy.reporting_delivery`` block for this producer.

    Returns a *whole* capability document, not a fragment, so the result can
    be validated against ``core/reporting-delivery-capabilities.json``
    before it is published. A fragment would push six required fields onto
    the caller to remember, and an under-filled capability block is exactly
    the kind of thing that passes review and fails a buyer's validator.

    The seller supplies what only it knows -- its ``offerings``, its
    seller-wide recovery window and retention. This method supplies the task
    names and the Reliable Reporting declarations, because those follow from
    the producer actually running rather than from configuration.

    ``consumer_status_task`` is an explicit argument rather than inferred:
    advertising it while the ingest is disabled is the half-implemented loop
    this module's docstring warns about, and a buyer that can file
    statements nobody reads believes it has told you.
    """
    return _advertised_reporting_delivery(
        escalation=self._escalation,
        consumer_status_task=consumer_status_task,
        offerings=offerings,
        automated_recovery_window=automated_recovery_window,
        status_retention_days=status_retention_days,
        extra=extra,
    )

The complete media_buy.reporting_delivery block for this producer.

Returns a whole capability document, not a fragment, so the result can be validated against core/reporting-delivery-capabilities.json before it is published. A fragment would push six required fields onto the caller to remember, and an under-filled capability block is exactly the kind of thing that passes review and fails a buyer's validator.

The seller supplies what only it knows – its offerings, its seller-wide recovery window and retention. This method supplies the task names and the Reliable Reporting declarations, because those follow from the producer actually running rather than from configuration.

consumer_status_task is an explicit argument rather than inferred: advertising it while the ingest is disabled is the half-implemented loop this module's docstring warns about, and a buyer that can file statements nobody reads believes it has told you.

async def close_elapsed_periods(self,
configuration: ReportingConfiguration,
*,
now: datetime | None = None) ‑> list[ReportingObligationRecord]
Expand source code
async def close_elapsed_periods(
    self, configuration: ReportingConfiguration, *, now: datetime | None = None
) -> list[ReportingObligationRecord]:
    """Commit an obligation for every elapsed eligible period.

    Public because a seller often wants to run this on its own cadence --
    the obligation must land in the first ledger snapshot strictly after the
    period boundary, independent of whether the source is healthy.
    """
    if configuration.quarantined:
        raise LedgerConflictError(
            "REPORTING_GENERATION_QUARANTINED", "legacy generations cannot run"
        )
    turn = WorkerTurn()
    return await self._close_elapsed_periods(configuration, turn, now=now or self._clock())

Commit an obligation for every elapsed eligible period.

Public because a seller often wants to run this on its own cadence – the obligation must land in the first ledger snapshot strictly after the period boundary, independent of whether the source is healthy.

async def commit_revision_from_manifest(self,
obligation: ReportingObligationRecord,
manifest: SourceBatchManifestV1,
*,
rows: Sequence[dict[str, Any]],
finality: str,
now: datetime | None = None,
turn: WorkerTurn | None = None,
acquisition: ProvisionalAcquisition | None = None) ‑> ReportingRevisionRecord
Expand source code
async def commit_revision_from_manifest(
    self,
    obligation: ReportingObligationRecord,
    manifest: SourceBatchManifestV1,
    *,
    rows: Sequence[dict[str, Any]],
    finality: str,
    now: datetime | None = None,
    turn: WorkerTurn | None = None,
    acquisition: ProvisionalAcquisition | None = None,
) -> ReportingRevisionRecord:
    """Project a verified manifest into an immutable ledger revision.

    A snapshot restatement supersedes the current snapshot leaf; there is no
    edit path.  An official close is terminal, so a later source correction
    must arrive as an adjustment instead. ``now`` is a trusted publication
    instant, unlike the dispatch instant accepted by ``acquire_obligation``.
    Replaying a publication retains its original creation time and parent.
    """
    obligation = await self._stored_obligation(obligation)
    self._validate_manifest_currency(obligation, manifest)
    if any(cell.status == "unsupported" for cell in manifest.metric_availability):
        try:
            offering = self._source.capabilities.offering(manifest.offering_id)
            _validate_metric_applicability(offering.metrics, manifest.metric_availability)
        except (KeyError, ValueError):
            raise LedgerConflictError(
                "MANIFEST_MISMATCH",
                "unsupported cells require partial metric support with a reason",
            ) from None
    now = now or self._clock()
    turn = turn or WorkerTurn()
    existing = await self._store.list_revisions(
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
    )
    selection = select_reporting_revision(
        existing,
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
        required_finality="snapshot",
    )
    # A retained official close coexists with the snapshot chain and wins
    # whole-history selection outright, so it is never the snapshot leaf.
    # Reading it as one would root a restatement at ``None`` and split the
    # obligation into two snapshot roots -- a permanently corrupt history
    # over immutable rows, with no repair path.
    leaf = select_reporting_revision(
        tuple(item for item in existing if item.finality == "snapshot"),
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
        required_finality="snapshot",
    )
    if selection.kind == "corrupt" or leaf.kind == "corrupt":
        raise LedgerConflictError("HISTORY_UNAVAILABLE", "the revision history requires repair")
    supersedes = (
        leaf.revision.reporting_revision_id
        if finality == "snapshot" and leaf.kind == "selected"
        else None
    )

    if acquisition is not None and finality == "snapshot":
        supersedes = acquisition.predecessor_revision_id

    control_totals = tuple((total.name, total.value) for total in manifest.control_totals)
    revision_id = f"rpr_{manifest.publication_id[4:44]}"
    prior = next((item for item in existing if item.reporting_revision_id == revision_id), None)
    created_at = prior.created_at if prior is not None else now
    if prior is not None:
        # Still reconstruct and verify the supplied content below. Merely
        # finding the ID must not bypass immutable-content validation.
        # On replay this retained parent wins over the acquisition's
        # predecessor, just as the retained creation time wins over now.
        supersedes = prior.supersedes_reporting_revision_id
    if (
        _utc(manifest.acquired_at) > _utc(now)
        or _utc(manifest.observed_at) > _utc(created_at)
        or _utc(manifest.finality_evidence.observed_at) > _utc(created_at)
        or (
            finality == "official"
            and _utc(manifest.finality_evidence.observed_at) < _utc(obligation.period.end)
        )
    ):
        raise LedgerConflictError(
            "PUBLICATION_TIME_INVALID",
            "source observation or finality is outside the publication time bounds; "
            "check source evidence and the producer clock before retrying",
        )
    revision = ReportingRevisionRecord(
        reporting_revision_id=revision_id,
        account_id=obligation.account_id,
        reporting_obligation_id=obligation.reporting_obligation_id,
        finality="official" if finality == "official" else "snapshot",
        revision_content_sha256=revision_content_sha256(
            reporting_revision_id=revision_id,
            row_count=manifest.row_count,
            control_totals=control_totals,
            reporting_rows=rows,
        ),
        row_count=manifest.row_count,
        control_totals=control_totals,
        observed_at=manifest.observed_at,
        data_through=manifest.data_through,
        created_at=created_at,
        supersedes_reporting_revision_id=supersedes,
        finality_basis=(
            (
                "stabilized"
                if manifest.finality_evidence.basis == "elapsed_settlement_window"
                else "source_final"
            )
            if finality == "official"
            else None
        ),
        finality_policy_id=(
            f"{obligation.report_definition_id}:{manifest.offering_id}"
            if finality == "official"
            else None
        ),
        finalized_at=manifest.finality_evidence.observed_at if finality == "official" else None,
        source_publication_id=manifest.publication_id,
        source_manifest_sha256=manifest.content_fingerprint.split(":", 1)[-1],
    )
    async with self._revision_publication(obligation, manifest.offering_id):
        if self._revision_verifier is not None:
            from adcp.reporting.materializer.publication import verified_publication

            revision = verified_publication(self._revision_verifier, obligation, revision, rows)
        if acquisition is None:
            committed = await self._store.commit_revision(revision, rows)
        else:
            # ``now`` is the publication anchor, sampled after row acquisition.
            # The bounds check above already rejects acquired_at after now.
            checked_at = _utc(now)
            boundary = manifest.finality_evidence.provisional_until or (
                _utc(obligation.period.end) + acquisition.policy.window
            )
            next_due = (
                min(checked_at + acquisition.policy.cadence, _utc(boundary))
                if finality == "snapshot" and checked_at < _utc(boundary)
                else None
            )
            committed = await self._observation_store().commit_provisional_observation(
                ProvisionalObservation(
                    acquisition,
                    revision_id,
                    checked_at,
                    _utc(boundary),
                    next_due,
                    manifest.model_dump_json(),
                ),
                revision,
                rows,
            )
    turn.revisions_committed.append(committed.reporting_revision_id)
    return committed

Project a verified manifest into an immutable ledger revision.

A snapshot restatement supersedes the current snapshot leaf; there is no edit path. An official close is terminal, so a later source correction must arrive as an adjustment instead. now is a trusted publication instant, unlike the dispatch instant accepted by acquire_obligation. Replaying a publication retains its original creation time and parent.

async def run_configuration(self,
configuration: ReportingConfiguration,
*,
now: datetime | None = None) ‑> WorkerTurn
Expand source code
async def run_configuration(
    self,
    configuration: ReportingConfiguration,
    *,
    now: datetime | None = None,
) -> WorkerTurn:
    """Run one turn for an already-routed configuration generation.

    High-level orchestrators use this entry point after freezing adapter,
    currency, and source scope for a specific generation. The orchestrator
    owns cross-process scheduling; obligation and revision writes remain
    convergent and immutable in the ledger store.

    Most adopters should continue using :meth:`run_worker`, whose store
    lease chooses a configuration automatically.
    """
    from adcp.reporting.production.contracts import _SourceAuthorizationRevokedError

    boundary = now or self._clock()
    turn = WorkerTurn()
    with source_turn():
        try:
            require_account_work(configuration.account_id)
            await self._close_elapsed_periods(configuration, turn, now=boundary)
            await self._acquire_pending(configuration, turn, now=boundary)
        except _SourceAuthorizationRevokedError:
            return turn
    return turn

Run one turn for an already-routed configuration generation.

High-level orchestrators use this entry point after freezing adapter, currency, and source scope for a specific generation. The orchestrator owns cross-process scheduling; obligation and revision writes remain convergent and immutable in the ledger store.

Most adopters should continue using :meth:run_worker, whose store lease chooses a configuration automatically.

async def run_worker(self) ‑> WorkerTurn
Expand source code
async def run_worker(self) -> WorkerTurn:
    """Run one leased turn. Safe to call from cron, a loop, or a supervisor.

    Returns immediately with an empty turn when nothing is leasable, so a
    caller can back off rather than spin. Standalone PostgreSQL turns retry
    deadlocks and lock timeouts at most twice, after rollback and fenced
    release. Each retry must acquire a new lease; other failures propagate.
    """
    from adcp.reporting.ledger.pg import PgReportingLedgerStore

    retryable = (
        self._store._worker_lock_errors()
        if isinstance(self._store, PgReportingLedgerStore)
        else ()
    )
    for attempt in range(3):
        leased = None
        with source_turn():
            try:
                now = self._clock()
                leased = await self._store.lease_period_close(
                    worker_id=self._worker_id, now=now, lease_seconds=self._lease_seconds
                )
                turn = WorkerTurn(leased=leased)
                if leased is None:
                    return turn
                await self._work_leased_configuration(leased, turn, now=now)
            except retryable:
                # Store transactions have already exited/rolled back. A
                # committed lease is released only under its original fence;
                # a competitor may win before the next acquisition.
                if leased is not None:
                    await self._release_worker_lease(leased, retryable)
                if attempt == 2:
                    raise
            except BaseException as original:
                if leased is not None:
                    try:
                        await self._release_worker_lease(leased, retryable)
                    except retryable:
                        # Never turn an immutable-content conflict or
                        # cancellation into a retryable cleanup failure.
                        raise original from None
                raise
            else:
                # Exhausted release retries escape from here, rather than
                # starting another turn or reporting a false empty success.
                await self._release_worker_lease(leased, retryable)
                return turn
        await asyncio.sleep(0.05 * (2**attempt))
    raise AssertionError("unreachable worker retry")  # pragma: no cover

Run one leased turn. Safe to call from cron, a loop, or a supervisor.

Returns immediately with an empty turn when nothing is leasable, so a caller can back off rather than spin. Standalone PostgreSQL turns retry deadlocks and lock timeouts at most twice, after rollback and fenced release. Each retry must acquire a new lease; other failures propagate.

class ReportingReceiptKey (principal: ReportingDeliveryPrincipal,
reporting_receipt_id: str)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReceiptKey(_ClosedValue):
    principal: ReportingDeliveryPrincipal
    reporting_receipt_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_receipt_id, maximum=255)
        if len(self.reporting_receipt_id) < 16:
            raise ValueError("a reporting receipt identifier requires at least 16 characters")

ReportingReceiptKey(principal: 'ReportingDeliveryPrincipal', reporting_receipt_id: 'str')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var principal : ReportingDeliveryPrincipal
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReceiptKey(_ClosedValue):
    principal: ReportingDeliveryPrincipal
    reporting_receipt_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_receipt_id, maximum=255)
        if len(self.reporting_receipt_id) < 16:
            raise ValueError("a reporting receipt identifier requires at least 16 characters")
var reporting_receipt_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReceiptKey(_ClosedValue):
    principal: ReportingDeliveryPrincipal
    reporting_receipt_id: str

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.reporting_receipt_id, maximum=255)
        if len(self.reporting_receipt_id) < 16:
            raise ValueError("a reporting receipt identifier requires at least 16 characters")
class ReportingReceiptStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingReceiptStore(Protocol):
    """Transport-derived scope is mandatory. Exact retries precede temporal checks.

    ``received_at`` is assigned by the store; callers retry the immutable input
    or the returned record. Both receipt kinds share the same scoped ID namespace.
    """

    async def record_revision_receipt(
        self, record: ReportingRevisionReceiptRecord
    ) -> tuple[ReportingRevisionReceiptRecord, bool]: ...

    async def record_adjustment_receipt(
        self, record: ReportingAdjustmentReceiptRecord
    ) -> tuple[ReportingAdjustmentReceiptRecord, bool]: ...

    async def get_receipt(self, key: ReportingReceiptKey) -> ReportingReceiptRecord | None: ...

Transport-derived scope is mandatory. Exact retries precede temporal checks.

received_at is assigned by the store; callers retry the immutable input or the returned record. Both receipt kinds share the same scoped ID namespace.

Ancestors

  • typing.Protocol
  • typing.Generic

Subclasses

Methods

async def get_receipt(self,
key: ReportingReceiptKey) ‑> ReportingRevisionReceiptRecord | ReportingAdjustmentReceiptRecord | None
Expand source code
async def get_receipt(self, key: ReportingReceiptKey) -> ReportingReceiptRecord | None: ...
async def record_adjustment_receipt(self,
record: ReportingAdjustmentReceiptRecord) ‑> tuple[ReportingAdjustmentReceiptRecord, bool]
Expand source code
async def record_adjustment_receipt(
    self, record: ReportingAdjustmentReceiptRecord
) -> tuple[ReportingAdjustmentReceiptRecord, bool]: ...
async def record_revision_receipt(self,
record: ReportingRevisionReceiptRecord) ‑> tuple[ReportingRevisionReceiptRecord, bool]
Expand source code
async def record_revision_receipt(
    self, record: ReportingRevisionReceiptRecord
) -> tuple[ReportingRevisionReceiptRecord, bool]: ...
class ReportingReconciliationChange (sequence: int, record: ReportingDeliveryRecord)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationChange:
    sequence: int
    record: ReportingDeliveryRecord

ReportingReconciliationChange(sequence: 'int', record: 'ReportingDeliveryRecord')

Instance variables

var record : ReportingDestinationBinding | ReportingObligationDeliveryRecord | ReportingMaterializationAttempt | ReportingMaterializationRecord | ReportingMaterializationCheck | ReportingRevisionReceiptRecord | ReportingAdjustmentReceiptRecord
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationChange:
    sequence: int
    record: ReportingDeliveryRecord
var sequence : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationChange:
    sequence: int
    record: ReportingDeliveryRecord
class ReportingReconciliationChangeStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingReconciliationFeedStore(Protocol):
    """An optional seam; existing Core and reconciliation protocols are unchanged.

    Persist partial pages with their cursor. Only the final page issues a
    checkpoint, which opens a new walk via ``changes_after``. Tokens bind caller,
    version, filters, bounds and the last emitted key, and survive reader restarts.
    Neither token is an authorization grant or a Core status checkpoint.
    """

    async def read_reconciliation_changes(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        changes_after: ReportingReconciliationCheckpoint | None = None,
        cursor: ReportingReconciliationCursor | None = None,
        limit: int = 100,
        filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
    ) -> ReportingReconciliationPage: ...

An optional seam; existing Core and reconciliation protocols are unchanged.

Persist partial pages with their cursor. Only the final page issues a checkpoint, which opens a new walk via changes_after. Tokens bind caller, version, filters, bounds and the last emitted key, and survive reader restarts. Neither token is an authorization grant or a Core status checkpoint.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def read_reconciliation_changes(self,
*,
caller: ReportingDeliveryPrincipal,
changes_after: ReportingReconciliationCheckpoint | None = None,
cursor: ReportingReconciliationCursor | None = None,
limit: int = 100,
filters: ReportingReconciliationFilter = ReportingReconciliationFilter(record_kinds=(), reporting_obligation_id=None)) ‑> ReportingReconciliationPage
Expand source code
async def read_reconciliation_changes(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    changes_after: ReportingReconciliationCheckpoint | None = None,
    cursor: ReportingReconciliationCursor | None = None,
    limit: int = 100,
    filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
) -> ReportingReconciliationPage: ...
class ReportingReconciliationFeedStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingReconciliationFeedStore(Protocol):
    """An optional seam; existing Core and reconciliation protocols are unchanged.

    Persist partial pages with their cursor. Only the final page issues a
    checkpoint, which opens a new walk via ``changes_after``. Tokens bind caller,
    version, filters, bounds and the last emitted key, and survive reader restarts.
    Neither token is an authorization grant or a Core status checkpoint.
    """

    async def read_reconciliation_changes(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        changes_after: ReportingReconciliationCheckpoint | None = None,
        cursor: ReportingReconciliationCursor | None = None,
        limit: int = 100,
        filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
    ) -> ReportingReconciliationPage: ...

An optional seam; existing Core and reconciliation protocols are unchanged.

Persist partial pages with their cursor. Only the final page issues a checkpoint, which opens a new walk via changes_after. Tokens bind caller, version, filters, bounds and the last emitted key, and survive reader restarts. Neither token is an authorization grant or a Core status checkpoint.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def read_reconciliation_changes(self,
*,
caller: ReportingDeliveryPrincipal,
changes_after: ReportingReconciliationCheckpoint | None = None,
cursor: ReportingReconciliationCursor | None = None,
limit: int = 100,
filters: ReportingReconciliationFilter = ReportingReconciliationFilter(record_kinds=(), reporting_obligation_id=None)) ‑> ReportingReconciliationPage
Expand source code
async def read_reconciliation_changes(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    changes_after: ReportingReconciliationCheckpoint | None = None,
    cursor: ReportingReconciliationCursor | None = None,
    limit: int = 100,
    filters: ReportingReconciliationFilter = ReportingReconciliationFilter(),
) -> ReportingReconciliationPage: ...
class ReportingReconciliationFilter (record_kinds: tuple[ReportingReconciliationRecordKind, ...] = (),
reporting_obligation_id: str | None = None)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationFilter:
    record_kinds: tuple[ReportingReconciliationRecordKind, ...] = ()
    reporting_obligation_id: str | None = None

    def __post_init__(self) -> None:
        if type(self.record_kinds) is not tuple or any(
            not _is_record_kind(kind) for kind in self.record_kinds
        ):
            raise ValueError("invalid reconciliation record filter")
        object.__setattr__(self, "record_kinds", tuple(sorted(set(self.record_kinds))))
        if self.reporting_obligation_id is not None:
            reporting_identifier(self.reporting_obligation_id)

    @property
    def fingerprint(self) -> str:
        return hashlib.sha256(canonical_json_utf8_v1(asdict(self))).hexdigest()

    def matches(self, record: ReportingDeliveryRecord) -> bool:
        return (not self.record_kinds or record.kind in self.record_kinds) and (
            self.reporting_obligation_id is None
            or (
                not isinstance(record, ReportingDestinationBinding)
                and record.scope.reporting_obligation_id == self.reporting_obligation_id
            )
        )

ReportingReconciliationFilter(record_kinds: 'tuple[ReportingReconciliationRecordKind, …]' = (), reporting_obligation_id: 'str | None' = None)

Instance variables

prop fingerprint : str
Expand source code
@property
def fingerprint(self) -> str:
    return hashlib.sha256(canonical_json_utf8_v1(asdict(self))).hexdigest()
var record_kinds : tuple[typing.Literal['destination_binding', 'obligation_delivery', 'materialization_attempt', 'materialization', 'materialization_check', 'revision_receipt', 'adjustment_receipt'], ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationFilter:
    record_kinds: tuple[ReportingReconciliationRecordKind, ...] = ()
    reporting_obligation_id: str | None = None

    def __post_init__(self) -> None:
        if type(self.record_kinds) is not tuple or any(
            not _is_record_kind(kind) for kind in self.record_kinds
        ):
            raise ValueError("invalid reconciliation record filter")
        object.__setattr__(self, "record_kinds", tuple(sorted(set(self.record_kinds))))
        if self.reporting_obligation_id is not None:
            reporting_identifier(self.reporting_obligation_id)

    @property
    def fingerprint(self) -> str:
        return hashlib.sha256(canonical_json_utf8_v1(asdict(self))).hexdigest()

    def matches(self, record: ReportingDeliveryRecord) -> bool:
        return (not self.record_kinds or record.kind in self.record_kinds) and (
            self.reporting_obligation_id is None
            or (
                not isinstance(record, ReportingDestinationBinding)
                and record.scope.reporting_obligation_id == self.reporting_obligation_id
            )
        )
var reporting_obligation_id : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationFilter:
    record_kinds: tuple[ReportingReconciliationRecordKind, ...] = ()
    reporting_obligation_id: str | None = None

    def __post_init__(self) -> None:
        if type(self.record_kinds) is not tuple or any(
            not _is_record_kind(kind) for kind in self.record_kinds
        ):
            raise ValueError("invalid reconciliation record filter")
        object.__setattr__(self, "record_kinds", tuple(sorted(set(self.record_kinds))))
        if self.reporting_obligation_id is not None:
            reporting_identifier(self.reporting_obligation_id)

    @property
    def fingerprint(self) -> str:
        return hashlib.sha256(canonical_json_utf8_v1(asdict(self))).hexdigest()

    def matches(self, record: ReportingDeliveryRecord) -> bool:
        return (not self.record_kinds or record.kind in self.record_kinds) and (
            self.reporting_obligation_id is None
            or (
                not isinstance(record, ReportingDestinationBinding)
                and record.scope.reporting_obligation_id == self.reporting_obligation_id
            )
        )

Methods

def matches(self, record: ReportingDeliveryRecord) ‑> bool
Expand source code
def matches(self, record: ReportingDeliveryRecord) -> bool:
    return (not self.record_kinds or record.kind in self.record_kinds) and (
        self.reporting_obligation_id is None
        or (
            not isinstance(record, ReportingDestinationBinding)
            and record.scope.reporting_obligation_id == self.reporting_obligation_id
        )
    )
class ReportingReconciliationPage (caller: ReportingDeliveryPrincipal,
boundary: ReportingReconciliationSnapshotToken,
changes: tuple[ReportingReconciliationChange, ...],
has_more: bool,
cursor: ReportingReconciliationCursor | None,
changes_checkpoint: ReportingReconciliationCheckpoint | None)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count

ReportingReconciliationPage(caller: 'ReportingDeliveryPrincipal', boundary: 'ReportingReconciliationSnapshotToken', changes: 'tuple[ReportingReconciliationChange, …]', has_more: 'bool', cursor: 'ReportingReconciliationCursor | None', changes_checkpoint: 'ReportingReconciliationCheckpoint | None')

Instance variables

var boundary : ReportingReconciliationSnapshotToken
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count
var caller : ReportingDeliveryPrincipal
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count
var changes : tuple[ReportingReconciliationChange, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count
var changes_checkpoint : adcp.reporting.ledger.delivery_changes.ReportingReconciliationCheckpoint | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count
var cursor : adcp.reporting.ledger.delivery_changes.ReportingReconciliationCursor | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count
var has_more : bool
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationPage:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    changes: tuple[ReportingReconciliationChange, ...]
    has_more: bool
    cursor: ReportingReconciliationCursor | None
    changes_checkpoint: ReportingReconciliationCheckpoint | None

    @property
    def total_count(self) -> int:
        return self.boundary.total_count
prop total_count : int
Expand source code
@property
def total_count(self) -> int:
    return self.boundary.total_count
class ReportingReconciliationSnapshot (caller: ReportingDeliveryPrincipal,
boundary: ReportingReconciliationSnapshotToken,
records: tuple[ReportingDeliveryRecord, ...])
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshot:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    records: tuple[ReportingDeliveryRecord, ...]

    def materialization(
        self, key: ReportingMaterializationKey
    ) -> ReportingMaterializationView | None:
        if key.principal != self.caller:
            return None
        attempt = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationAttempt) and item.key == key
            ),
            None,
        )
        if attempt is None:
            return None
        binding = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingDestinationBinding)
                and item.generation_key == attempt.scope.generation_key
            ),
            None,
        )
        if binding is None:
            unavailable()
        outcome = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationRecord) and item.key == key
            ),
            None,
        )
        return ReportingMaterializationView(
            attempt,
            binding,
            outcome,
            tuple(
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationCheck)
                and item.reporting_materialization_id == key.reporting_materialization_id
            ),
        )

    @property
    def current_receipts(self) -> tuple[ReportingReceiptRecord, ...]:
        receipts = tuple(
            item
            for item in self.records
            if isinstance(item, (ReportingRevisionReceiptRecord, ReportingAdjustmentReceiptRecord))
        )
        return tuple(item for item in receipts if current_receipt(self.records, item) == item)

    @property
    def terminal_acceptances(self) -> tuple[ReportingReceiptKey, ...]:
        return tuple(item.key for item in self.current_receipts if item.status == "accepted")

ReportingReconciliationSnapshot(caller: 'ReportingDeliveryPrincipal', boundary: 'ReportingReconciliationSnapshotToken', records: 'tuple[ReportingDeliveryRecord, …]')

Instance variables

var boundary : ReportingReconciliationSnapshotToken
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshot:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    records: tuple[ReportingDeliveryRecord, ...]

    def materialization(
        self, key: ReportingMaterializationKey
    ) -> ReportingMaterializationView | None:
        if key.principal != self.caller:
            return None
        attempt = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationAttempt) and item.key == key
            ),
            None,
        )
        if attempt is None:
            return None
        binding = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingDestinationBinding)
                and item.generation_key == attempt.scope.generation_key
            ),
            None,
        )
        if binding is None:
            unavailable()
        outcome = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationRecord) and item.key == key
            ),
            None,
        )
        return ReportingMaterializationView(
            attempt,
            binding,
            outcome,
            tuple(
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationCheck)
                and item.reporting_materialization_id == key.reporting_materialization_id
            ),
        )

    @property
    def current_receipts(self) -> tuple[ReportingReceiptRecord, ...]:
        receipts = tuple(
            item
            for item in self.records
            if isinstance(item, (ReportingRevisionReceiptRecord, ReportingAdjustmentReceiptRecord))
        )
        return tuple(item for item in receipts if current_receipt(self.records, item) == item)

    @property
    def terminal_acceptances(self) -> tuple[ReportingReceiptKey, ...]:
        return tuple(item.key for item in self.current_receipts if item.status == "accepted")
var caller : ReportingDeliveryPrincipal
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshot:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    records: tuple[ReportingDeliveryRecord, ...]

    def materialization(
        self, key: ReportingMaterializationKey
    ) -> ReportingMaterializationView | None:
        if key.principal != self.caller:
            return None
        attempt = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationAttempt) and item.key == key
            ),
            None,
        )
        if attempt is None:
            return None
        binding = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingDestinationBinding)
                and item.generation_key == attempt.scope.generation_key
            ),
            None,
        )
        if binding is None:
            unavailable()
        outcome = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationRecord) and item.key == key
            ),
            None,
        )
        return ReportingMaterializationView(
            attempt,
            binding,
            outcome,
            tuple(
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationCheck)
                and item.reporting_materialization_id == key.reporting_materialization_id
            ),
        )

    @property
    def current_receipts(self) -> tuple[ReportingReceiptRecord, ...]:
        receipts = tuple(
            item
            for item in self.records
            if isinstance(item, (ReportingRevisionReceiptRecord, ReportingAdjustmentReceiptRecord))
        )
        return tuple(item for item in receipts if current_receipt(self.records, item) == item)

    @property
    def terminal_acceptances(self) -> tuple[ReportingReceiptKey, ...]:
        return tuple(item.key for item in self.current_receipts if item.status == "accepted")
prop current_receipts : tuple[ReportingReceiptRecord, ...]
Expand source code
@property
def current_receipts(self) -> tuple[ReportingReceiptRecord, ...]:
    receipts = tuple(
        item
        for item in self.records
        if isinstance(item, (ReportingRevisionReceiptRecord, ReportingAdjustmentReceiptRecord))
    )
    return tuple(item for item in receipts if current_receipt(self.records, item) == item)
var records : tuple[ReportingDestinationBinding | ReportingObligationDeliveryRecord | ReportingMaterializationAttempt | ReportingMaterializationRecord | ReportingMaterializationCheck | ReportingRevisionReceiptRecord | ReportingAdjustmentReceiptRecord, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshot:
    caller: ReportingDeliveryPrincipal
    boundary: ReportingReconciliationSnapshotToken
    records: tuple[ReportingDeliveryRecord, ...]

    def materialization(
        self, key: ReportingMaterializationKey
    ) -> ReportingMaterializationView | None:
        if key.principal != self.caller:
            return None
        attempt = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationAttempt) and item.key == key
            ),
            None,
        )
        if attempt is None:
            return None
        binding = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingDestinationBinding)
                and item.generation_key == attempt.scope.generation_key
            ),
            None,
        )
        if binding is None:
            unavailable()
        outcome = next(
            (
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationRecord) and item.key == key
            ),
            None,
        )
        return ReportingMaterializationView(
            attempt,
            binding,
            outcome,
            tuple(
                item
                for item in self.records
                if isinstance(item, ReportingMaterializationCheck)
                and item.reporting_materialization_id == key.reporting_materialization_id
            ),
        )

    @property
    def current_receipts(self) -> tuple[ReportingReceiptRecord, ...]:
        receipts = tuple(
            item
            for item in self.records
            if isinstance(item, (ReportingRevisionReceiptRecord, ReportingAdjustmentReceiptRecord))
        )
        return tuple(item for item in receipts if current_receipt(self.records, item) == item)

    @property
    def terminal_acceptances(self) -> tuple[ReportingReceiptKey, ...]:
        return tuple(item.key for item in self.current_receipts if item.status == "accepted")
prop terminal_acceptances : tuple[ReportingReceiptKey, ...]
Expand source code
@property
def terminal_acceptances(self) -> tuple[ReportingReceiptKey, ...]:
    return tuple(item.key for item in self.current_receipts if item.status == "accepted")

Methods

def materialization(self,
key: ReportingMaterializationKey) ‑> ReportingMaterializationView | None
Expand source code
def materialization(
    self, key: ReportingMaterializationKey
) -> ReportingMaterializationView | None:
    if key.principal != self.caller:
        return None
    attempt = next(
        (
            item
            for item in self.records
            if isinstance(item, ReportingMaterializationAttempt) and item.key == key
        ),
        None,
    )
    if attempt is None:
        return None
    binding = next(
        (
            item
            for item in self.records
            if isinstance(item, ReportingDestinationBinding)
            and item.generation_key == attempt.scope.generation_key
        ),
        None,
    )
    if binding is None:
        unavailable()
    outcome = next(
        (
            item
            for item in self.records
            if isinstance(item, ReportingMaterializationRecord) and item.key == key
        ),
        None,
    )
    return ReportingMaterializationView(
        attempt,
        binding,
        outcome,
        tuple(
            item
            for item in self.records
            if isinstance(item, ReportingMaterializationCheck)
            and item.reporting_materialization_id == key.reporting_materialization_id
        ),
    )
class ReportingReconciliationSnapshotToken (caller: ReportingDeliveryPrincipal,
snapshot_id: str,
ledger_as_of: datetime,
min_sequence: int,
max_sequence: int,
total_count: int,
filters: ReportingReconciliationFilter)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id

A frozen boundary in one principal's feed, never a Core ledger sequence.

Instance variables

prop account_id : str
Expand source code
@property
def account_id(self) -> str:
    return self.caller.account_id
var caller : ReportingDeliveryPrincipal
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
prop consumer_id : str
Expand source code
@property
def consumer_id(self) -> str:
    return self.caller.consumer_id
var filters : ReportingReconciliationFilter
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
var ledger_as_of : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
var max_sequence : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
var min_sequence : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
var snapshot_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
var total_count : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingReconciliationSnapshotToken:
    """A frozen boundary in one principal's feed, never a Core ledger sequence."""

    caller: ReportingDeliveryPrincipal
    snapshot_id: str
    ledger_as_of: datetime
    min_sequence: int
    max_sequence: int
    total_count: int
    filters: ReportingReconciliationFilter

    @property
    def account_id(self) -> str:
        return self.caller.account_id

    @property
    def consumer_id(self) -> str:
        return self.caller.consumer_id
class ReportingReconciliationStore (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingReconciliationStore(
    ReportingDestinationStore, ReportingMaterializationStore, ReportingReceiptStore, Protocol
):
    async def read_reconciliation_snapshot(
        self,
        *,
        caller: ReportingDeliveryPrincipal,
        boundary: ReportingReconciliationSnapshotToken | None = None,
    ) -> ReportingReconciliationSnapshot: ...

Trusted configuration ingestion. Never accept a destination from a receipt body.

Ancestors

Methods

async def read_reconciliation_snapshot(self,
*,
caller: ReportingDeliveryPrincipal,
boundary: ReportingReconciliationSnapshotToken | None = None) ‑> ReportingReconciliationSnapshot
Expand source code
async def read_reconciliation_snapshot(
    self,
    *,
    caller: ReportingDeliveryPrincipal,
    boundary: ReportingReconciliationSnapshotToken | None = None,
) -> ReportingReconciliationSnapshot: ...
class ReportingResourceRecord (resource_ref: str,
kind: "Literal['manifest', 'dataset', 'warehouse_relation']",
location: str,
immutability: "Literal['immutable_location', 'native_version']",
expires_at: datetime,
native_version_ref: str | None = None,
manifest_sha256: str | None = None,
object_refs: tuple[str, ...] = (),
reader_compatibility: tuple[str, ...] = ())
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")

ReportingResourceRecord(resource_ref: 'str', kind: "Literal['manifest', 'dataset', 'warehouse_relation']", location: 'str', immutability: "Literal['immutable_location', 'native_version']", expires_at: 'datetime', native_version_ref: 'str | None' = None, manifest_sha256: 'str | None' = None, object_refs: 'tuple[str, …]' = (), reader_compatibility: 'tuple[str, …]' = ())

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var expires_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var immutability : Literal['immutable_location', 'native_version']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var kind : Literal['manifest', 'dataset', 'warehouse_relation']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var location : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var manifest_sha256 : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var native_version_ref : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var object_refs : tuple[str, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var reader_compatibility : tuple[str, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
var resource_ref : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingResourceRecord(_ClosedValue):
    resource_ref: str
    kind: Literal["manifest", "dataset", "warehouse_relation"]
    location: str
    immutability: Literal["immutable_location", "native_version"]
    expires_at: datetime
    native_version_ref: str | None = None
    manifest_sha256: str | None = None
    object_refs: tuple[str, ...] = ()
    reader_compatibility: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        _freeze_fields(self)
        reporting_identifier(self.resource_ref, maximum=255)
        resource_location(self.location)
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.manifest_sha256 is not None:
            sha256_value(self.manifest_sha256)
        if self.kind == "manifest" and self.manifest_sha256 is None:
            raise ValueError("manifest resources require the exact manifest digest")
        if self.immutability == "native_version" and self.native_version_ref is None:
            raise ValueError("native-version resources require an immutable version")
        object.__setattr__(self, "expires_at", aware_utc(self.expires_at))
        object.__setattr__(
            self,
            "object_refs",
            tuple(file_object_reference(value) for value in self.object_refs),
        )
        object.__setattr__(
            self,
            "reader_compatibility",
            tuple(reader_feature_reference(value) for value in self.reader_compatibility),
        )
        if len(set(self.object_refs)) != len(self.object_refs):
            raise ValueError("resource object references must be unique")
        if len(set(self.reader_compatibility)) != len(self.reader_compatibility):
            raise ValueError("reader compatibility requirements must be unique")
class ReportingRevisionCorrupt (reason: "Literal['ownership_mismatch', 'duplicate_revision_id', 'invalid_revision_identity', 'invalid_finality', 'missing_predecessor', 'cross_finality_edge', 'official_predecessor', 'forked_snapshot_history', 'disconnected_snapshot_history', 'revision_cycle', 'multiple_officials']")
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionCorrupt:
    # Closed diagnostics: no row data, credentials or provider errors.
    reason: Literal[
        "ownership_mismatch",
        "duplicate_revision_id",
        "invalid_revision_identity",
        "invalid_finality",
        "missing_predecessor",
        "cross_finality_edge",
        "official_predecessor",
        "forked_snapshot_history",
        "disconnected_snapshot_history",
        "revision_cycle",
        "multiple_officials",
    ]
    kind: Literal["corrupt"] = field(default="corrupt", init=False)

ReportingRevisionCorrupt(reason: "Literal['ownership_mismatch', 'duplicate_revision_id', 'invalid_revision_identity', 'invalid_finality', 'missing_predecessor', 'cross_finality_edge', 'official_predecessor', 'forked_snapshot_history', 'disconnected_snapshot_history', 'revision_cycle', 'multiple_officials']")

Instance variables

var kind : Literal['corrupt']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionCorrupt:
    # Closed diagnostics: no row data, credentials or provider errors.
    reason: Literal[
        "ownership_mismatch",
        "duplicate_revision_id",
        "invalid_revision_identity",
        "invalid_finality",
        "missing_predecessor",
        "cross_finality_edge",
        "official_predecessor",
        "forked_snapshot_history",
        "disconnected_snapshot_history",
        "revision_cycle",
        "multiple_officials",
    ]
    kind: Literal["corrupt"] = field(default="corrupt", init=False)
var reason : Literal['ownership_mismatch', 'duplicate_revision_id', 'invalid_revision_identity', 'invalid_finality', 'missing_predecessor', 'cross_finality_edge', 'official_predecessor', 'forked_snapshot_history', 'disconnected_snapshot_history', 'revision_cycle', 'multiple_officials']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionCorrupt:
    # Closed diagnostics: no row data, credentials or provider errors.
    reason: Literal[
        "ownership_mismatch",
        "duplicate_revision_id",
        "invalid_revision_identity",
        "invalid_finality",
        "missing_predecessor",
        "cross_finality_edge",
        "official_predecessor",
        "forked_snapshot_history",
        "disconnected_snapshot_history",
        "revision_cycle",
        "multiple_officials",
    ]
    kind: Literal["corrupt"] = field(default="corrupt", init=False)
class ReportingRevisionNotReady (reason: "Literal['empty_history', 'official_required']")
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionNotReady:
    reason: Literal["empty_history", "official_required"]
    kind: Literal["not_ready"] = field(default="not_ready", init=False)

ReportingRevisionNotReady(reason: "Literal['empty_history', 'official_required']")

Instance variables

var kind : Literal['not_ready']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionNotReady:
    reason: Literal["empty_history", "official_required"]
    kind: Literal["not_ready"] = field(default="not_ready", init=False)
var reason : Literal['empty_history', 'official_required']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionNotReady:
    reason: Literal["empty_history", "official_required"]
    kind: Literal["not_ready"] = field(default="not_ready", init=False)
class ReportingRevisionReceiptRecord (scope: ReportingDeliveryScope,
reporting_receipt_id: str,
reporting_revision_id: str,
reporting_materialization_id: str,
status: ReceiptStatus,
verification_profile: VerificationProfile,
observed_row_count: int,
observed_control_totals: tuple[ReportingControlTotalRecord, ...],
observed_at: datetime,
supersedes_reporting_receipt_id: str | None = None,
observed_canonical_content_digest: ReportingCanonicalDigest | None = None,
observed_manifest_sha256: str | None = None,
observed_native_version_ref: str | None = None,
consumer_commit_ref: str | None = None,
rejection_codes: tuple[str, ...] = (),
received_at: datetime | None = None,
*,
kind: "Literal['revision_receipt']" = 'revision_receipt')
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)

ReportingRevisionReceiptRecord(scope: 'ReportingDeliveryScope', reporting_receipt_id: 'str', reporting_revision_id: 'str', reporting_materialization_id: 'str', status: 'ReceiptStatus', verification_profile: 'VerificationProfile', observed_row_count: 'int', observed_control_totals: 'tuple[ReportingControlTotalRecord, …]', observed_at: 'datetime', supersedes_reporting_receipt_id: 'str | None' = None, observed_canonical_content_digest: 'ReportingCanonicalDigest | None' = None, observed_manifest_sha256: 'str | None' = None, observed_native_version_ref: 'str | None' = None, consumer_commit_ref: 'str | None' = None, rejection_codes: 'tuple[str, …]' = (), received_at: 'datetime | None' = None, *, kind: "Literal['revision_receipt']" = 'revision_receipt')

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var consumer_commit_ref : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
prop key : ReportingReceiptKey
Expand source code
@property
def key(self) -> ReportingReceiptKey:
    return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var kind : Literal['revision_receipt']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_canonical_content_digest : ReportingCanonicalDigest | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_control_totals : tuple[ReportingControlTotalRecord, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_manifest_sha256 : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_native_version_ref : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var observed_row_count : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var received_at : datetime.datetime | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var rejection_codes : tuple[str, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var reporting_materialization_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var reporting_receipt_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var reporting_revision_id : str
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var scope : ReportingDeliveryScope
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var status : Literal['accepted', 'rejected']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var supersedes_reporting_receipt_id : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
var verification_profile : Literal['canonical_digest', 'manifest_checksums', 'native_commit']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionReceiptRecord(_ClosedValue):
    scope: ReportingDeliveryScope
    reporting_receipt_id: str
    reporting_revision_id: str
    reporting_materialization_id: str
    status: ReceiptStatus
    verification_profile: VerificationProfile
    observed_row_count: int
    observed_control_totals: tuple[ReportingControlTotalRecord, ...]
    observed_at: datetime
    supersedes_reporting_receipt_id: str | None = None
    observed_canonical_content_digest: ReportingCanonicalDigest | None = None
    observed_manifest_sha256: str | None = None
    observed_native_version_ref: str | None = None
    consumer_commit_ref: str | None = None
    rejection_codes: tuple[str, ...] = ()
    received_at: datetime | None = None
    kind: Literal["revision_receipt"] = field(default="revision_receipt", kw_only=True)

    def __post_init__(self) -> None:
        _freeze_fields(self)
        self.key
        reporting_identifier(self.reporting_revision_id, maximum=255)
        reporting_identifier(self.reporting_materialization_id, maximum=255)
        _positive(self.observed_row_count, zero=True)
        _unique_totals(self.observed_control_totals)
        if self.observed_manifest_sha256 is not None:
            sha256_value(self.observed_manifest_sha256)
        if self.observed_native_version_ref is not None:
            native_version_reference(self.observed_native_version_ref)
        if self.consumer_commit_ref is not None:
            consumer_commit_reference(self.consumer_commit_ref)
        _receipt_fields(self)

    @property
    def key(self) -> ReportingReceiptKey:
        return ReportingReceiptKey(self.scope.principal, self.reporting_receipt_id)
class ReportingRevisionRecord (reporting_revision_id: str,
account_id: str,
reporting_obligation_id: str,
finality: ReportingFinality,
revision_content_sha256: str,
row_count: int,
control_totals: tuple[tuple[str, str], ...],
observed_at: datetime,
data_through: datetime | None,
created_at: datetime,
supersedes_reporting_revision_id: str | None = None,
finality_basis: "Literal['source_final', 'contractual_cutoff', 'stabilized'] | None" = None,
finality_policy_id: str | None = None,
finalized_at: datetime | None = None,
readable: bool = True,
readable_at_commit: bool = True,
source_publication_id: str | None = None,
source_manifest_sha256: str | None = None,
canonical_content_digest: ReportingCanonicalDigest | None = None,
managed_control_totals: tuple[ReportingControlTotalRecord, ...] | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingRevisionRecord:
    """One immutable publication of a period's content.

    ``revision_content_sha256`` is the Core binding: JCS over
    ``{reporting_revision_id, row_count, control_totals, reporting_rows}``,
    SHA-256 of those bytes.  A consumer recomputes it from what it actually
    read, which is what makes an exact read verifiable rather than trusted.

    ``readable`` is not cosmetic.  Core's promise is that a committed revision
    stays readable for ``status_retention_days``; an obligation whose only
    qualifying revision has become unreadable is ``action_required``, not
    ``complete``.
    """

    reporting_revision_id: str
    account_id: str
    reporting_obligation_id: str
    finality: ReportingFinality
    revision_content_sha256: str
    row_count: int
    control_totals: tuple[tuple[str, str], ...]
    observed_at: datetime
    data_through: datetime | None
    created_at: datetime
    supersedes_reporting_revision_id: str | None = None
    finality_basis: Literal["source_final", "contractual_cutoff", "stabilized"] | None = None
    finality_policy_id: str | None = None
    finalized_at: datetime | None = None
    readable: bool = True
    readable_at_commit: bool = True
    source_publication_id: str | None = None
    source_manifest_sha256: str | None = None
    # Optional managed evidence supplied by a trusted publisher before any
    # destination work. Core neither computes nor requires this contract.
    canonical_content_digest: ReportingCanonicalDigest | None = None
    managed_control_totals: tuple[ReportingControlTotalRecord, ...] | None = None

    def __post_init__(self) -> None:
        if (
            self.canonical_content_digest is not None
            and type(self.canonical_content_digest) is not ReportingCanonicalDigest
        ):
            raise ValueError("managed revision evidence requires an immutable canonical digest")
        object.__setattr__(
            self, "control_totals", tuple(tuple(item) for item in self.control_totals)
        )
        if self.managed_control_totals is not None:
            object.__setattr__(
                self,
                "managed_control_totals",
                freeze_control_totals(self.managed_control_totals, self.control_totals),
            )
        if self.finality == "official":
            if not (self.finality_basis and self.finality_policy_id and self.finalized_at):
                raise ValueError(
                    "an official revision needs finality basis, policy, and finalized_at; "
                    "without them a consumer cannot tell contractual close from a guess"
                )
        elif self.finality_basis or self.finality_policy_id or self.finalized_at:
            raise ValueError("finality evidence belongs only to an official revision")
        if self.finality == "official" and self.supersedes_reporting_revision_id:
            raise ValueError(
                "an official revision is terminal and cannot supersede another revision; "
                "publish a later source correction as an adjustment"
            )

One immutable publication of a period's content.

revision_content_sha256() is the Core binding: JCS over {reporting_revision_id, row_count, control_totals, reporting_rows}, SHA-256 of those bytes. A consumer recomputes it from what it actually read, which is what makes an exact read verifiable rather than trusted.

readable is not cosmetic. Core's promise is that a committed revision stays readable for status_retention_days; an obligation whose only qualifying revision has become unreadable is action_required, not complete.

Instance variables

var account_id : str
var canonical_content_digest : ReportingCanonicalDigest | None
var control_totals : tuple[tuple[str, str], ...]
var created_at : datetime.datetime
var data_through : datetime.datetime | None
var finality : Literal['snapshot', 'official']
var finality_basis : Literal['source_final', 'contractual_cutoff', 'stabilized'] | None
var finality_policy_id : str | None
var finalized_at : datetime.datetime | None
var managed_control_totals : tuple[ReportingControlTotalRecord, ...] | None
var observed_at : datetime.datetime
var readable : bool
var readable_at_commit : bool
var reporting_obligation_id : str
var reporting_revision_id : str
var revision_content_sha256 : str
var row_count : int
var source_manifest_sha256 : str | None
var source_publication_id : str | None
var supersedes_reporting_revision_id : str | None
class ReportingRevisionSelected (revision: R)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionSelected(Generic[R]):
    revision: R
    kind: Literal["selected"] = field(default="selected", init=False)

ReportingRevisionSelected(revision: 'R')

Ancestors

  • typing.Generic

Instance variables

var kind : Literal['selected']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionSelected(Generic[R]):
    revision: R
    kind: Literal["selected"] = field(default="selected", init=False)
var revision : ~R
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingRevisionSelected(Generic[R]):
    revision: R
    kind: Literal["selected"] = field(default="selected", init=False)
class ReportingRowPage (rows: tuple[dict[str, Any], ...],
total_count: int,
has_more: bool,
cursor: str | None,
reporting_revision_id: str | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingRowPage:
    """One page of an exact revision read.

    Every page repeats identical revision metadata and binding; 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.
    """

    rows: tuple[dict[str, Any], ...]
    total_count: int
    has_more: bool
    cursor: str | None
    reporting_revision_id: str | None = None

One page of an exact revision read.

Every page repeats identical revision metadata and binding; 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.

Instance variables

var cursor : str | None
var has_more : bool
var reporting_revision_id : str | None
var rows : tuple[dict[str, typing.Any], ...]
var total_count : int
class ReportingScheduleSpec (period_duration: str,
delivery_sla: str,
alignment: "Literal['utc', 'account_timezone', 'custom_timezone']" = 'utc',
period_timezone: str | None = None,
period_anchor: datetime | None = None)
Expand source code
@dataclass(frozen=True)
class ReportingScheduleSpec:
    """The clock a reporting configuration creates.

    ``expected_at`` is always ``period.end + delivery_sla``.  Accepting a media
    buy does not start a separate SLA; the configuration is the only clock.
    """

    period_duration: str
    delivery_sla: str
    alignment: Literal["utc", "account_timezone", "custom_timezone"] = "utc"
    period_timezone: str | None = None
    period_anchor: datetime | None = None

    def timezone_name(self, account_timezone: str) -> str:
        if self.alignment == "utc":
            return "UTC"
        if self.alignment == "account_timezone":
            return account_timezone
        if not self.period_timezone:
            raise ValueError("custom_timezone alignment requires period_timezone")
        return self.period_timezone

The clock a reporting configuration creates.

expected_at is always period.end + delivery_sla. Accepting a media buy does not start a separate SLA; the configuration is the only clock.

Instance variables

var alignment : Literal['utc', 'account_timezone', 'custom_timezone']
var delivery_sla : str
var period_anchor : datetime.datetime | None
var period_duration : str
var period_timezone : str | None

Methods

def timezone_name(self, account_timezone: str) ‑> str
Expand source code
def timezone_name(self, account_timezone: str) -> str:
    if self.alignment == "utc":
        return "UTC"
    if self.alignment == "account_timezone":
        return account_timezone
    if not self.period_timezone:
        raise ValueError("custom_timezone alignment requires period_timezone")
    return self.period_timezone
class ReportingStatusCaller (account_id: str, consumer_id: str)
Expand source code
@dataclass(frozen=True)
class ReportingStatusCaller:
    """The authenticated caller, resolved from transport, never from the body.

    ``consumer_id`` is what scopes consumer-status visibility.  A request body
    that asserts a buyer principal is ignored: identity comes from the
    authenticated transport or it does not exist.
    """

    account_id: str
    consumer_id: str

    def __post_init__(self) -> None:
        from adcp.reporting.evidence import consumer_reference, principal_reference

        principal_reference(self.account_id)
        consumer_reference(self.consumer_id)

The authenticated caller, resolved from transport, never from the body.

consumer_id is what scopes consumer-status visibility. A request body that asserts a buyer principal is ignored: identity comes from the authenticated transport or it does not exist.

Instance variables

var account_id : str
var consumer_id : str
class ReportingStatusHandler (store: ReportingLedgerStore,
*,
page_size: int = 100,
consumer_status_enabled: bool = False,
escalation: ReportingDeliveryEscalation | None = None)
Expand source code
class ReportingStatusHandler:
    """Render one captured status snapshot using the same pure projection as push."""

    def __init__(
        self,
        store: ReportingLedgerStore,
        *,
        page_size: int = _DEFAULT_PAGE_SIZE,
        consumer_status_enabled: bool = False,
        escalation: ReportingDeliveryEscalation | None = None,
    ) -> None:
        self._store = store
        self._page_size = page_size
        self._consumer_status_enabled = consumer_status_enabled
        self._escalation = escalation

    async def handle(
        self, request: dict[str, Any], *, caller: ReportingStatusCaller
    ) -> dict[str, Any]:
        snapshot = await self._load(caller)
        return self.render_snapshot(request, caller=caller, snapshot=snapshot)

    async def _load(self, caller: ReportingStatusCaller) -> ReportingStatusSnapshot:
        from adcp.reporting.ledger.status_snapshot import ReportingStatusParticipant

        if isinstance(self._store, ReportingStatusParticipant):
            return await self._store.read_status_snapshot(caller=caller)
        # Optional upgrade: custom stores keep their original structural API.
        # This fallback cannot establish durable status-notification readiness.
        boundary = await self._store.open_snapshot(
            caller=caller, filters_fingerprint="status-projection-v1"
        )
        configurations = await self._store.list_configurations(caller=caller)
        obligations: list[ReportingObligationRecord] = []
        revisions: list[ReportingRevisionRecord] = []
        adjustments: list[ReportingAdjustmentRecord] = []
        statuses: list[ConsumerStatusRecord] = []
        offset = 0
        changes: list[tuple[int, str, str, str]] = []
        while True:
            page = await self._store.read_page(
                snapshot=boundary,
                consumer_id=caller.consumer_id,
                delivery_config_ids=None,
                media_buy_ids=None,
                offset=offset,
                limit=self._page_size,
                changes_after_sequence=None,
            )
            obligations.extend(page.obligations)
            revisions.extend(page.revisions)
            adjustments.extend(page.adjustments)
            statuses.extend(page.consumer_statuses)
            if not page.has_more:
                break
            offset += self._page_size
        lifecycles = []
        for key in sorted({mismatch_key(s) for s in statuses}):
            seen: set[str] = set()
            while (
                issue := await self._store.get_issue(account_id=caller.account_id, issue_key=key)
            ) is not None:
                if issue.issue_id in seen or issue.issue_key != key:
                    raise LedgerConflictError(
                        "STATUS_PROJECTION_UNAVAILABLE", "invalid waiver chain"
                    )
                seen.add(issue.issue_id)
                lifecycles.append(issue)
                if issue.issue_state != "waived":
                    break
                key = condition_after_waiver(issue)
        for kind, records, attribute in (
            ("obligation", obligations, "reporting_obligation_id"),
            ("revision", revisions, "reporting_revision_id"),
            ("adjustment", adjustments, "reporting_adjustment_id"),
            ("consumer_status", statuses, "reporting_status_id"),
        ):
            # The legacy page API has a durable boundary but no per-record
            # ordinals. Replay its retained records when that boundary advances;
            # incremental_repair permits identity-deduplicated over-inclusion.
            # Positional ordinals would instead omit later records after rebuilds.
            changes.extend(
                (boundary.max_sequence, kind, getattr(record, attribute), caller.consumer_id)
                for record in records
            )
        snapshot = ReportingStatusSnapshot(
            caller.account_id,
            boundary.ledger_as_of,
            tuple(configurations),
            tuple(obligations),
            tuple(revisions),
            tuple(statuses),
            tuple(lifecycles),
            adjustments=tuple(adjustments),
            changes=tuple(changes),
        )
        for _ in range(3):
            intents = lifecycle_intents(snapshot)
            if not intents:
                return snapshot
            for intent in intents:
                issue = intent.lifecycle
                if intent.action == "ensure_mismatch":
                    await self._store.ensure_issue_opened(
                        issue_key=issue.issue_key,
                        account_id=issue.account_id,
                        consumer_id=issue.consumer_id,
                        observed_at=issue.opened_at,
                    )
                elif intent.action == "retire_mismatch":
                    await self._store.retire_issue(
                        issue_key=issue.issue_key, account_id=issue.account_id, at=snapshot.as_of
                    )
            snapshot = apply_intents_to_snapshot(snapshot, intents)
        raise LedgerConflictError(
            "STATUS_PROJECTION_UNAVAILABLE", "status lifecycle did not converge"
        )

    def render_snapshot(
        self,
        request: dict[str, Any],
        *,
        caller: ReportingStatusCaller,
        snapshot: ReportingStatusSnapshot,
        reconciliation: tuple[ReportingDeliveryRecord, ...] | None = None,
        revision_ownership: bool = False,
    ) -> dict[str, Any]:
        """Render captured database evidence without any store calls or clock reads."""
        view = request.get("view", "summary")
        if view not in {"summary", "periods", "revision"}:
            raise LedgerConflictError("INVALID_VIEW", "unsupported reporting status view")
        if snapshot.account_id != caller.account_id:
            raise LedgerConflictError("LOOKUP_UNAVAILABLE", "status is unavailable to this caller")
        filters = _filters(request)
        version = normalize_to_release_precision(
            request.get("adcp_version") or resolve_adcp_version(None)
        )
        complete_start_forecast = is_adcp_version_at_least(version, "3.2-rc.6")
        if complete_start_forecast:
            # Bind the new wire contract without relabelling explicit rc.3 snapshots.
            filters["adcp_version"] = version
        scope = ReportingStatusScope(
            caller.account_id,
            consumer_id=caller.consumer_id,
        )
        from adcp.reporting.ledger.delivery_models import ReportingDeliveryPrincipal
        from adcp.reporting.materializer.capture import private_snapshot

        snapshot = private_snapshot(
            snapshot, ReportingDeliveryPrincipal(caller.account_id, caller.consumer_id)
        )
        snapshot_id = (
            "rpls_"
            + _fingerprint(
                [
                    caller.account_id,
                    caller.consumer_id,
                    filters,
                    snapshot.max_sequence,
                ]
            )[:32]
        )
        if reconciliation is not None:
            from adcp.reporting.ledger._delivery_state import fingerprint, principal

            reconciliation = tuple(
                r
                for r in reconciliation
                if principal(r).account_id == caller.account_id
                and principal(r).consumer_id == caller.consumer_id
            )
            snapshot_id = (
                "rpls_"
                + _fingerprint(
                    [snapshot_id, [fingerprint(r) for r in reconciliation], _iso(snapshot.as_of)]
                )[:32]
            )
        offset = 0
        lower = _checkpoint_sequence(request.get("changes_after"), caller=caller) or 0
        cursor = (request.get("pagination") or {}).get("cursor")
        if cursor:
            decoded = decode_cursor(cursor)
            _require_owned_cursor(decoded, caller=caller)
            if decoded.get("snapshot") != snapshot_id:
                raise LedgerConflictError(
                    "CURSOR_SNAPSHOT_MISMATCH",
                    "this cursor belongs to a different snapshot, caller or filter set;"
                    " restart the walk",
                )
            cursor_lower = decoded.get("lower")
            if not isinstance(cursor_lower, int):
                raise LedgerConflictError(
                    "CURSOR_SNAPSHOT_MISMATCH",
                    "this cursor does not retain its incremental lower bound; restart the walk",
                )
            if "changes_after" in request and lower != cursor_lower:
                raise LedgerConflictError(
                    "CURSOR_SNAPSHOT_MISMATCH",
                    "this cursor belongs to a different incremental lower bound; restart the walk",
                )
            lower = cursor_lower
            offset = int(decoded.get("offset", 0))
            if "as_of" in decoded:
                snapshot = replace(snapshot, as_of=datetime.fromisoformat(decoded["as_of"]))
        value = StatusProjectionInput(
            snapshot,
            scope,
            self._escalation,
            tuple(filters["delivery_config_ids"] or ()),
            tuple(filters["media_buy_ids"] or ()),
            tuple(filters["feed_purposes"] or ()),
            _parse(filters["period_start"]),
            _parse(filters["period_end"]),
            reconciliation=reconciliation,
            consumer_status_enabled=self._consumer_status_enabled,
        )
        result = project_status_scope(value)
        if result.intents:
            raise LedgerConflictError(
                "STATUS_PROJECTION_UNAVAILABLE", "status lifecycle is pending"
            )
        common: dict[str, Any] = {
            "status": "completed",
            "view": view,
            "ledger_snapshot_id": snapshot_id,
            "ledger_as_of": _iso(snapshot.as_of),
            "account_id": caller.account_id,
        }
        if view == "revision":
            revision_id = request.get("reporting_revision_id")
            if not revision_id:
                raise LedgerConflictError(
                    "MISSING_REVISION_ID", "a revision view requires reporting_revision_id"
                )
            revision = next(
                (r for r in snapshot.revisions if r.reporting_revision_id == revision_id), None
            )
            if revision is None:
                raise LedgerConflictError(
                    "LOOKUP_UNAVAILABLE", "no such revision is available to this caller"
                )
            owner = next(
                (
                    o
                    for o in snapshot.obligations
                    if o.reporting_obligation_id == revision.reporting_obligation_id
                ),
                None,
            )
            response = {
                **common,
                "revision": _revision_to_wire(revision, owner),
                "adjustments": [
                    _adjustment_to_wire(a)
                    for a in snapshot.adjustments
                    if a.adjusts_reporting_revision_id == revision_id
                ],
                "materializations": [],
                "receipts": [],
                "pagination": {"total_count": 1, "has_more": False},
            }
            if reconciliation is not None:
                from adcp.reporting.projection.wire import exact_revision_evidence

                response.update(
                    exact_revision_evidence(snapshot, revision, owner, reconciliation, caller)
                )
                if revision_ownership:
                    from adcp.reporting.ownership import with_revision_ownership

                    response = with_revision_ownership(
                        response, {revision.reporting_revision_id: revision.reporting_obligation_id}
                    )
            return response
        common["scope"] = _scope_to_wire(
            result.configurations,
            ledger_as_of=snapshot.as_of,
            request=request,
            obligations=tuple(p.obligation for p in result.obligations),
        )
        obligations = tuple(p.obligation for p in result.obligations)
        if view == "summary":
            states: list[str] = [p.projection.health for p in result.obligations]
            counts = {
                "total": len(obligations),
                **{
                    state: states.count(state)
                    for state in ("waiting", "healthy", "delayed", "action_required", "complete")
                },
            }
            if self._consumer_status_enabled:
                counts["consumer_status_pending"] = result.pending_count
            watermark = _scope_data_through(p.projection for p in result.obligations)
            configurations = tuple(
                c
                for c in result.configurations
                if (not request.get("finality") or c.required_finality in request["finality"])
                and (
                    not request.get("health")
                    or project_status_scope(
                        replace(
                            value,
                            scope=ReportingStatusScope(
                                c.account_id, c.generation_key, consumer_id=scope.consumer_id
                            ),
                        )
                    ).health
                    in request["health"]
                )
            )
            selected_obligations = tuple(
                p.obligation
                for p in result.obligations
                if (
                    not request.get("finality")
                    or p.obligation.required_finality in request["finality"]
                )
                and (not request.get("health") or p.projection.health in request["health"])
            )
            next_expected = (
                next_reporting_period_start(configurations, as_of=snapshot.as_of)
                if complete_start_forecast and result.health == "complete"
                else next_reporting_expectation(
                    configurations,
                    selected_obligations,
                    as_of=snapshot.as_of,
                    period_start=value.period_start,
                    period_end=value.period_end,
                )
            )
            return {
                **common,
                "health": result.health,
                "coverage": _coverage_roll_up(obligations, as_of=snapshot.as_of),
                "data_through": _iso(watermark) if watermark else None,
                **({"next_expected_at": _iso(next_expected)} if next_expected is not None else {}),
                "obligation_counts": counts,
                "issues": [i.to_wire() for i in result.issues],
            }
        owners = {o.reporting_obligation_id: o for o in obligations}
        records: dict[tuple[str, str], Any] = {
            ("obligation", o.reporting_obligation_id): o for o in obligations
        }
        for r in snapshot.revisions:
            if r.reporting_obligation_id in owners:
                records[("revision", r.reporting_revision_id)] = r
        for a in snapshot.adjustments:
            if ("revision", a.adjusts_reporting_revision_id) in records:
                records[("adjustment", a.reporting_adjustment_id)] = a
        generation_keys = {c.generation_key for c in result.configurations}
        if self._consumer_status_enabled:
            for s in snapshot.statuses:
                if s.consumer_id == caller.consumer_id and s.generation_key in generation_keys:
                    if (value.period_start is not None and s.period_end <= value.period_start) or (
                        value.period_end is not None and s.period_start >= value.period_end
                    ):
                        continue
                    records[("consumer_status", s.reporting_status_id)] = s
        selected = [
            (kind, records[(kind, record_id)])
            for seq, kind, record_id, _ in sorted(snapshot.changes)
            if seq > lower and (kind, record_id) in records
        ]
        window = selected[offset : offset + self._page_size]
        has_more = offset + self._page_size < len(selected)
        periods = []
        for kind, record in window:
            if kind != "obligation":
                continue
            scoped = project_status_scope(
                replace(value, scope=ReportingStatusScope.for_obligation(record, scope.consumer_id))
            )
            item = scoped.obligations[0]
            periods.append(
                _obligation_to_wire(
                    record,
                    revisions=item.revisions,
                    health=scoped.health,
                    production_status=item.projection.production_status,
                    issues=scoped.issues,
                    statuses=item.statuses,
                )
            )
        payload = {
            **common,
            "health": result.health,
            "issues": [issue.to_wire() for issue in result.issues],
            "changes_checkpoint": _encode_checkpoint(snapshot.max_sequence, caller=caller),
            "periods": periods,
            "revisions": [
                _revision_to_wire(r, owners.get(r.reporting_obligation_id))
                for kind, r in window
                if kind == "revision"
            ],
            "adjustments": [_adjustment_to_wire(a) for kind, a in window if kind == "adjustment"],
            "materializations": [],
            "receipts": [],
            "pagination": {
                "total_count": len(selected),
                "has_more": has_more,
                **(
                    {
                        "cursor": encode_cursor(
                            {
                                "ownership": 2,
                                "account": caller.account_id,
                                "consumer": caller.consumer_id,
                                "snapshot": snapshot_id,
                                "offset": offset + self._page_size,
                                "lower": lower,
                                "as_of": snapshot.as_of.isoformat(),
                            }
                        )
                    }
                    if has_more
                    else {}
                ),
            },
        }
        if self._consumer_status_enabled:
            payload["consumer_statuses"] = [
                _consumer_status_to_wire(s) for kind, s in window if kind == "consumer_status"
            ]
        return payload

Render one captured status snapshot using the same pure projection as push.

Methods

async def handle(self,
request: dict[str, Any],
*,
caller: ReportingStatusCaller) ‑> dict[str, typing.Any]
Expand source code
async def handle(
    self, request: dict[str, Any], *, caller: ReportingStatusCaller
) -> dict[str, Any]:
    snapshot = await self._load(caller)
    return self.render_snapshot(request, caller=caller, snapshot=snapshot)
def render_snapshot(self,
request: dict[str, Any],
*,
caller: ReportingStatusCaller,
snapshot: ReportingStatusSnapshot,
reconciliation: tuple[ReportingDeliveryRecord, ...] | None = None,
revision_ownership: bool = False) ‑> dict[str, typing.Any]
Expand source code
def render_snapshot(
    self,
    request: dict[str, Any],
    *,
    caller: ReportingStatusCaller,
    snapshot: ReportingStatusSnapshot,
    reconciliation: tuple[ReportingDeliveryRecord, ...] | None = None,
    revision_ownership: bool = False,
) -> dict[str, Any]:
    """Render captured database evidence without any store calls or clock reads."""
    view = request.get("view", "summary")
    if view not in {"summary", "periods", "revision"}:
        raise LedgerConflictError("INVALID_VIEW", "unsupported reporting status view")
    if snapshot.account_id != caller.account_id:
        raise LedgerConflictError("LOOKUP_UNAVAILABLE", "status is unavailable to this caller")
    filters = _filters(request)
    version = normalize_to_release_precision(
        request.get("adcp_version") or resolve_adcp_version(None)
    )
    complete_start_forecast = is_adcp_version_at_least(version, "3.2-rc.6")
    if complete_start_forecast:
        # Bind the new wire contract without relabelling explicit rc.3 snapshots.
        filters["adcp_version"] = version
    scope = ReportingStatusScope(
        caller.account_id,
        consumer_id=caller.consumer_id,
    )
    from adcp.reporting.ledger.delivery_models import ReportingDeliveryPrincipal
    from adcp.reporting.materializer.capture import private_snapshot

    snapshot = private_snapshot(
        snapshot, ReportingDeliveryPrincipal(caller.account_id, caller.consumer_id)
    )
    snapshot_id = (
        "rpls_"
        + _fingerprint(
            [
                caller.account_id,
                caller.consumer_id,
                filters,
                snapshot.max_sequence,
            ]
        )[:32]
    )
    if reconciliation is not None:
        from adcp.reporting.ledger._delivery_state import fingerprint, principal

        reconciliation = tuple(
            r
            for r in reconciliation
            if principal(r).account_id == caller.account_id
            and principal(r).consumer_id == caller.consumer_id
        )
        snapshot_id = (
            "rpls_"
            + _fingerprint(
                [snapshot_id, [fingerprint(r) for r in reconciliation], _iso(snapshot.as_of)]
            )[:32]
        )
    offset = 0
    lower = _checkpoint_sequence(request.get("changes_after"), caller=caller) or 0
    cursor = (request.get("pagination") or {}).get("cursor")
    if cursor:
        decoded = decode_cursor(cursor)
        _require_owned_cursor(decoded, caller=caller)
        if decoded.get("snapshot") != snapshot_id:
            raise LedgerConflictError(
                "CURSOR_SNAPSHOT_MISMATCH",
                "this cursor belongs to a different snapshot, caller or filter set;"
                " restart the walk",
            )
        cursor_lower = decoded.get("lower")
        if not isinstance(cursor_lower, int):
            raise LedgerConflictError(
                "CURSOR_SNAPSHOT_MISMATCH",
                "this cursor does not retain its incremental lower bound; restart the walk",
            )
        if "changes_after" in request and lower != cursor_lower:
            raise LedgerConflictError(
                "CURSOR_SNAPSHOT_MISMATCH",
                "this cursor belongs to a different incremental lower bound; restart the walk",
            )
        lower = cursor_lower
        offset = int(decoded.get("offset", 0))
        if "as_of" in decoded:
            snapshot = replace(snapshot, as_of=datetime.fromisoformat(decoded["as_of"]))
    value = StatusProjectionInput(
        snapshot,
        scope,
        self._escalation,
        tuple(filters["delivery_config_ids"] or ()),
        tuple(filters["media_buy_ids"] or ()),
        tuple(filters["feed_purposes"] or ()),
        _parse(filters["period_start"]),
        _parse(filters["period_end"]),
        reconciliation=reconciliation,
        consumer_status_enabled=self._consumer_status_enabled,
    )
    result = project_status_scope(value)
    if result.intents:
        raise LedgerConflictError(
            "STATUS_PROJECTION_UNAVAILABLE", "status lifecycle is pending"
        )
    common: dict[str, Any] = {
        "status": "completed",
        "view": view,
        "ledger_snapshot_id": snapshot_id,
        "ledger_as_of": _iso(snapshot.as_of),
        "account_id": caller.account_id,
    }
    if view == "revision":
        revision_id = request.get("reporting_revision_id")
        if not revision_id:
            raise LedgerConflictError(
                "MISSING_REVISION_ID", "a revision view requires reporting_revision_id"
            )
        revision = next(
            (r for r in snapshot.revisions if r.reporting_revision_id == revision_id), None
        )
        if revision is None:
            raise LedgerConflictError(
                "LOOKUP_UNAVAILABLE", "no such revision is available to this caller"
            )
        owner = next(
            (
                o
                for o in snapshot.obligations
                if o.reporting_obligation_id == revision.reporting_obligation_id
            ),
            None,
        )
        response = {
            **common,
            "revision": _revision_to_wire(revision, owner),
            "adjustments": [
                _adjustment_to_wire(a)
                for a in snapshot.adjustments
                if a.adjusts_reporting_revision_id == revision_id
            ],
            "materializations": [],
            "receipts": [],
            "pagination": {"total_count": 1, "has_more": False},
        }
        if reconciliation is not None:
            from adcp.reporting.projection.wire import exact_revision_evidence

            response.update(
                exact_revision_evidence(snapshot, revision, owner, reconciliation, caller)
            )
            if revision_ownership:
                from adcp.reporting.ownership import with_revision_ownership

                response = with_revision_ownership(
                    response, {revision.reporting_revision_id: revision.reporting_obligation_id}
                )
        return response
    common["scope"] = _scope_to_wire(
        result.configurations,
        ledger_as_of=snapshot.as_of,
        request=request,
        obligations=tuple(p.obligation for p in result.obligations),
    )
    obligations = tuple(p.obligation for p in result.obligations)
    if view == "summary":
        states: list[str] = [p.projection.health for p in result.obligations]
        counts = {
            "total": len(obligations),
            **{
                state: states.count(state)
                for state in ("waiting", "healthy", "delayed", "action_required", "complete")
            },
        }
        if self._consumer_status_enabled:
            counts["consumer_status_pending"] = result.pending_count
        watermark = _scope_data_through(p.projection for p in result.obligations)
        configurations = tuple(
            c
            for c in result.configurations
            if (not request.get("finality") or c.required_finality in request["finality"])
            and (
                not request.get("health")
                or project_status_scope(
                    replace(
                        value,
                        scope=ReportingStatusScope(
                            c.account_id, c.generation_key, consumer_id=scope.consumer_id
                        ),
                    )
                ).health
                in request["health"]
            )
        )
        selected_obligations = tuple(
            p.obligation
            for p in result.obligations
            if (
                not request.get("finality")
                or p.obligation.required_finality in request["finality"]
            )
            and (not request.get("health") or p.projection.health in request["health"])
        )
        next_expected = (
            next_reporting_period_start(configurations, as_of=snapshot.as_of)
            if complete_start_forecast and result.health == "complete"
            else next_reporting_expectation(
                configurations,
                selected_obligations,
                as_of=snapshot.as_of,
                period_start=value.period_start,
                period_end=value.period_end,
            )
        )
        return {
            **common,
            "health": result.health,
            "coverage": _coverage_roll_up(obligations, as_of=snapshot.as_of),
            "data_through": _iso(watermark) if watermark else None,
            **({"next_expected_at": _iso(next_expected)} if next_expected is not None else {}),
            "obligation_counts": counts,
            "issues": [i.to_wire() for i in result.issues],
        }
    owners = {o.reporting_obligation_id: o for o in obligations}
    records: dict[tuple[str, str], Any] = {
        ("obligation", o.reporting_obligation_id): o for o in obligations
    }
    for r in snapshot.revisions:
        if r.reporting_obligation_id in owners:
            records[("revision", r.reporting_revision_id)] = r
    for a in snapshot.adjustments:
        if ("revision", a.adjusts_reporting_revision_id) in records:
            records[("adjustment", a.reporting_adjustment_id)] = a
    generation_keys = {c.generation_key for c in result.configurations}
    if self._consumer_status_enabled:
        for s in snapshot.statuses:
            if s.consumer_id == caller.consumer_id and s.generation_key in generation_keys:
                if (value.period_start is not None and s.period_end <= value.period_start) or (
                    value.period_end is not None and s.period_start >= value.period_end
                ):
                    continue
                records[("consumer_status", s.reporting_status_id)] = s
    selected = [
        (kind, records[(kind, record_id)])
        for seq, kind, record_id, _ in sorted(snapshot.changes)
        if seq > lower and (kind, record_id) in records
    ]
    window = selected[offset : offset + self._page_size]
    has_more = offset + self._page_size < len(selected)
    periods = []
    for kind, record in window:
        if kind != "obligation":
            continue
        scoped = project_status_scope(
            replace(value, scope=ReportingStatusScope.for_obligation(record, scope.consumer_id))
        )
        item = scoped.obligations[0]
        periods.append(
            _obligation_to_wire(
                record,
                revisions=item.revisions,
                health=scoped.health,
                production_status=item.projection.production_status,
                issues=scoped.issues,
                statuses=item.statuses,
            )
        )
    payload = {
        **common,
        "health": result.health,
        "issues": [issue.to_wire() for issue in result.issues],
        "changes_checkpoint": _encode_checkpoint(snapshot.max_sequence, caller=caller),
        "periods": periods,
        "revisions": [
            _revision_to_wire(r, owners.get(r.reporting_obligation_id))
            for kind, r in window
            if kind == "revision"
        ],
        "adjustments": [_adjustment_to_wire(a) for kind, a in window if kind == "adjustment"],
        "materializations": [],
        "receipts": [],
        "pagination": {
            "total_count": len(selected),
            "has_more": has_more,
            **(
                {
                    "cursor": encode_cursor(
                        {
                            "ownership": 2,
                            "account": caller.account_id,
                            "consumer": caller.consumer_id,
                            "snapshot": snapshot_id,
                            "offset": offset + self._page_size,
                            "lower": lower,
                            "as_of": snapshot.as_of.isoformat(),
                        }
                    )
                }
                if has_more
                else {}
            ),
        },
    }
    if self._consumer_status_enabled:
        payload["consumer_statuses"] = [
            _consumer_status_to_wire(s) for kind, s in window if kind == "consumer_status"
        ]
    return payload

Render captured database evidence without any store calls or clock reads.

class ReportingStatusNotificationHandler (status: ReportingStatusHandler,
*,
resolve_caller: ReportingStatusCallerResolver,
adcp_version: str | None = None)
Expand source code
class ReportingStatusNotificationHandler(ADCPHandler[ToolContext]):
    """Compose with adopter task overrides; mounts only the authoritative read.

    ``resolve_caller`` resolves the requested account using trusted transport
    authentication and account authorization. Request consumer/principal fields
    are never identity. Registration and other reporting tasks remain explicit
    adopter overrides, and do not appear merely because this handler exists.
    """

    def __init__(
        self,
        status: ReportingStatusHandler,
        *,
        resolve_caller: ReportingStatusCallerResolver,
        adcp_version: str | None = None,
    ) -> None:
        super().__init__()
        self.reporting_status_handler = status
        self._resolve_status_caller = resolve_caller
        self._adcp_version = resolve_adcp_version(adcp_version)
        if not is_adcp_version_at_least(self._adcp_version, "3.2-rc.6"):
            raise ConfigurationError(
                "reporting mounts require a supported AdCP 3.2 reporting contract; "
                "use 3.2 or omit the pin for the packaged default"
            )

    def get_adcp_version(self) -> str:
        """Public per-mount protocol pin, shared by schema and rendering."""
        return self._adcp_version

    async def get_reporting_status(
        self, params: GetReportingStatusRequest | dict[str, Any], context: ToolContext | None = None
    ) -> dict[str, Any]:
        request = (
            dict(params)
            if isinstance(params, dict)
            else params.model_dump(mode="json", exclude_unset=True)
        )
        request["adcp_version"] = (
            context.resolved_adcp_version
            if context is not None and context.resolved_adcp_version is not None
            else request.get("adcp_version") or self.get_adcp_version()
        )
        caller = await self._resolve_status_caller(request, context)
        return await self.reporting_status_handler.handle(request, caller=caller)

Compose with adopter task overrides; mounts only the authoritative read.

resolve_caller resolves the requested account using trusted transport authentication and account authorization. Request consumer/principal fields are never identity. Registration and other reporting tasks remain explicit adopter overrides, and do not appear merely because this handler exists.

Ancestors

Methods

def get_adcp_version(self) ‑> str
Expand source code
def get_adcp_version(self) -> str:
    """Public per-mount protocol pin, shared by schema and rendering."""
    return self._adcp_version

Public per-mount protocol pin, shared by schema and rendering.

Inherited members

class ReportingStatusParticipant (*args, **kwargs)
Expand source code
@runtime_checkable
class ReportingStatusParticipant(Protocol):
    """Optional upgrade; existing ReportingLedgerStore implementations stay valid."""

    async def read_status_snapshot(self, *, caller: ReportingCaller) -> ReportingStatusSnapshot: ...

    async def record_consumer_status_with_lifecycle(
        self, status: ConsumerStatusRecord
    ) -> tuple[ConsumerStatusRecord, bool]: ...

Optional upgrade; existing ReportingLedgerStore implementations stay valid.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def read_status_snapshot(self,
*,
caller: ReportingCaller) ‑> ReportingStatusSnapshot
Expand source code
async def read_status_snapshot(self, *, caller: ReportingCaller) -> ReportingStatusSnapshot: ...
async def record_consumer_status_with_lifecycle(self,
status: ConsumerStatusRecord) ‑> tuple[ConsumerStatusRecord, bool]
Expand source code
async def record_consumer_status_with_lifecycle(
    self, status: ConsumerStatusRecord
) -> tuple[ConsumerStatusRecord, bool]: ...
class ReportingStatusSnapshot (account_id: str,
as_of: datetime,
configurations: tuple[ReportingConfiguration, ...] = (),
obligations: tuple[ReportingObligationRecord, ...] = (),
revisions: tuple[ReportingRevisionRecord, ...] = (),
statuses: tuple[ConsumerStatusRecord, ...] = (),
lifecycles: tuple[ReportingIssueLifecycle, ...] = (),
issue_scopes: tuple[tuple[str, ReportingStatusScope], ...] = (),
consumer_ids: tuple[str, ...] = (),
adjustments: tuple[ReportingAdjustmentRecord, ...] = (),
changes: tuple[tuple[int, str, str, str], ...] = ())
Expand source code
@dataclass(frozen=True)
class ReportingStatusSnapshot:
    account_id: str
    as_of: datetime
    configurations: tuple[ReportingConfiguration, ...] = ()
    obligations: tuple[ReportingObligationRecord, ...] = ()
    revisions: tuple[ReportingRevisionRecord, ...] = ()
    statuses: tuple[ConsumerStatusRecord, ...] = ()
    lifecycles: tuple[ReportingIssueLifecycle, ...] = ()
    issue_scopes: tuple[tuple[str, ReportingStatusScope], ...] = ()
    consumer_ids: tuple[str, ...] = ()
    adjustments: tuple[ReportingAdjustmentRecord, ...] = ()
    # Sequence, record kind, ID, authenticated generation/status owner.
    changes: tuple[tuple[int, str, str, str], ...] = ()

    @property
    def max_sequence(self) -> int:
        return max((row[0] for row in self.changes), default=0)

ReportingStatusSnapshot(account_id: 'str', as_of: 'datetime', configurations: 'tuple[ReportingConfiguration, …]' = (), obligations: 'tuple[ReportingObligationRecord, …]' = (), revisions: 'tuple[ReportingRevisionRecord, …]' = (), statuses: 'tuple[ConsumerStatusRecord, …]' = (), lifecycles: 'tuple[ReportingIssueLifecycle, …]' = (), issue_scopes: 'tuple[tuple[str, ReportingStatusScope], …]' = (), consumer_ids: 'tuple[str, …]' = (), adjustments: 'tuple[ReportingAdjustmentRecord, …]' = (), changes: 'tuple[tuple[int, str, str, str], …]' = ())

Instance variables

var account_id : str
var adjustments : tuple[ReportingAdjustmentRecord, ...]
var as_of : datetime.datetime
var changes : tuple[tuple[int, str, str, str], ...]
var configurations : tuple[ReportingConfiguration, ...]
var consumer_ids : tuple[str, ...]
var issue_scopes : tuple[tuple[str, ReportingStatusScope], ...]
var lifecycles : tuple[ReportingIssueLifecycle, ...]
prop max_sequence : int
Expand source code
@property
def max_sequence(self) -> int:
    return max((row[0] for row in self.changes), default=0)
var obligations : tuple[ReportingObligationRecord, ...]
var revisions : tuple[ReportingRevisionRecord, ...]
var statuses : tuple[ConsumerStatusRecord, ...]
class ReportingVerificationRecord (verified_at: datetime,
verification_path: VerificationPath,
verification_profile: VerificationProfile,
row_count: int,
control_totals: tuple[ReportingControlTotalRecord, ...],
canonical_content_digest: ReportingCanonicalDigest | None = None,
physical_checksums: tuple[ReportingPhysicalChecksum, ...] = (),
native_version_ref: str | None = None,
native_observed_through: "Literal['representative_consumer', 'destination'] | None" = None,
verified_format: ReportingFormat | None = None)
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")

ReportingVerificationRecord(verified_at: 'datetime', verification_path: 'VerificationPath', verification_profile: 'VerificationProfile', row_count: 'int', control_totals: 'tuple[ReportingControlTotalRecord, …]', canonical_content_digest: 'ReportingCanonicalDigest | None' = None, physical_checksums: 'tuple[ReportingPhysicalChecksum, …]' = (), native_version_ref: 'str | None' = None, native_observed_through: "Literal['representative_consumer', 'destination'] | None" = None, verified_format: 'ReportingFormat | None' = None)

Ancestors

  • adcp.reporting.ledger.delivery_models._ClosedValue

Instance variables

var canonical_content_digest : ReportingCanonicalDigest | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var control_totals : tuple[ReportingControlTotalRecord, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var native_observed_through : Literal['representative_consumer', 'destination'] | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var native_version_ref : str | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var physical_checksums : tuple[ReportingPhysicalChecksum, ...]
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var row_count : int
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var verification_path : Literal['adcp.reporting.ledger.producer', 'representative_consumer', 'destination']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var verification_profile : Literal['canonical_digest', 'manifest_checksums', 'native_commit']
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var verified_at : datetime.datetime
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
var verified_format : Literal['jsonl', 'csv', 'parquet', 'avro', 'orc'] | None
Expand source code
@dataclass(frozen=True, slots=True)
class ReportingVerificationRecord(_ClosedValue):
    verified_at: datetime
    verification_path: VerificationPath
    verification_profile: VerificationProfile
    row_count: int
    control_totals: tuple[ReportingControlTotalRecord, ...]
    canonical_content_digest: ReportingCanonicalDigest | None = None
    physical_checksums: tuple[ReportingPhysicalChecksum, ...] = ()
    native_version_ref: str | None = None
    native_observed_through: Literal["representative_consumer", "destination"] | None = None
    verified_format: ReportingFormat | None = None

    def __post_init__(self) -> None:
        _freeze_fields(self)
        object.__setattr__(self, "verified_at", aware_utc(self.verified_at))
        _positive(self.row_count, zero=True)
        _unique_totals(self.control_totals)
        object.__setattr__(self, "physical_checksums", tuple(self.physical_checksums))
        if self.native_version_ref is not None:
            native_version_reference(self.native_version_ref)
        if self.native_observed_through is not None and self.native_version_ref is None:
            raise ValueError("native observation paths require version evidence")
class RestatementCheckpoint (account_id: str,
reporting_obligation_id: str,
checked_at: datetime,
next_observation: int,
provisional_until: datetime | None = None)
Expand source code
@dataclass(frozen=True)
class RestatementCheckpoint:
    """Durable scheduling state for successful source observations.

    ``next_observation`` advances even when the source content is unchanged.
    Without that durable ordinal the next scheduled refresh would replay the
    same sealed source execution forever instead of making a new observation.
    """

    account_id: str
    reporting_obligation_id: str
    checked_at: datetime
    next_observation: int
    provisional_until: datetime | None = None

    def __post_init__(self) -> None:
        if self.next_observation < 1:
            raise ValueError("next_observation must be at least one")
        _utc(self.checked_at)
        if self.provisional_until is not None:
            _utc(self.provisional_until)

Durable scheduling state for successful source observations.

next_observation advances even when the source content is unchanged. Without that durable ordinal the next scheduled refresh would replay the same sealed source execution forever instead of making a new observation.

Instance variables

var account_id : str
var checked_at : datetime.datetime
var next_observation : int
var provisional_until : datetime.datetime | None
var reporting_obligation_id : str
class RestatementCheckpointStore (*args, **kwargs)
Expand source code
@runtime_checkable
class RestatementCheckpointStore(Protocol):
    """Optional store extension used only by source settling policies."""

    async def get_restatement_checkpoint(
        self, *, account_id: str, reporting_obligation_id: str
    ) -> RestatementCheckpoint | None: ...

    async def record_restatement_checkpoint(
        self, checkpoint: RestatementCheckpoint
    ) -> RestatementCheckpoint:
        """Persist the next source observation ordinal after a successful read."""
        ...

Optional store extension used only by source settling policies.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def get_restatement_checkpoint(self, *, account_id: str, reporting_obligation_id: str) ‑> RestatementCheckpoint | None
Expand source code
async def get_restatement_checkpoint(
    self, *, account_id: str, reporting_obligation_id: str
) -> RestatementCheckpoint | None: ...
async def record_restatement_checkpoint(self,
checkpoint: RestatementCheckpoint) ‑> RestatementCheckpoint
Expand source code
async def record_restatement_checkpoint(
    self, checkpoint: RestatementCheckpoint
) -> RestatementCheckpoint:
    """Persist the next source observation ordinal after a successful read."""
    ...

Persist the next source observation ordinal after a successful read.

class RetryScheduleEntry (scope_key: str,
retry_not_before: datetime,
attempt: int,
blocked: bool = False,
recorded_at: datetime | None = None,
replayed: bool = False)
Expand source code
@dataclass(frozen=True)
class RetryScheduleEntry:
    """The next permitted source read for one slice, account, or source scope.

    ``attempt`` counts consecutive failures. Zero clears the schedule after a
    successful read. Scope keys are generated by the producer, not callers.
    """

    scope_key: str
    retry_not_before: datetime
    attempt: int
    blocked: bool = False
    recorded_at: datetime | None = None
    replayed: bool = field(default=False, compare=False, repr=False)

    def __post_init__(self) -> None:
        if not self.scope_key or self.attempt < 0:
            raise ValueError("retry schedule requires a scope key and nonnegative attempt")
        _utc(self.retry_not_before)
        if self.recorded_at is None:
            object.__setattr__(self, "recorded_at", self.retry_not_before)
        assert self.recorded_at is not None
        _utc(self.recorded_at)

The next permitted source read for one slice, account, or source scope.

attempt counts consecutive failures. Zero clears the schedule after a successful read. Scope keys are generated by the producer, not callers.

Instance variables

var attempt : int
var blocked : bool
var recorded_at : datetime.datetime | None
var replayed : bool
var retry_not_before : datetime.datetime
var scope_key : str
class RetryScheduleStore (*args, **kwargs)
Expand source code
@runtime_checkable
class RetryScheduleStore(Protocol):
    """Optional shared retry state for producer source acquisitions."""

    async def get_retry_schedule(self, *, scope_key: str) -> RetryScheduleEntry | None:
        pass

    async def record_retry_schedule(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
        pass

Optional shared retry state for producer source acquisitions.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def get_retry_schedule(self, *, scope_key: str) ‑> RetryScheduleEntry | None
Expand source code
async def get_retry_schedule(self, *, scope_key: str) -> RetryScheduleEntry | None:
    pass
async def record_retry_schedule(self,
entry: RetryScheduleEntry) ‑> RetryScheduleEntry
Expand source code
async def record_retry_schedule(self, entry: RetryScheduleEntry) -> RetryScheduleEntry:
    pass
class StatusLifecycleIntent (action: "Literal['ensure_mismatch', 'retire_mismatch', 'refine_scope']",
lifecycle: ReportingIssueLifecycle,
scope: ReportingStatusScope)
Expand source code
@dataclass(frozen=True)
class StatusLifecycleIntent:
    action: Literal["ensure_mismatch", "retire_mismatch", "refine_scope"]
    lifecycle: ReportingIssueLifecycle
    scope: ReportingStatusScope

StatusLifecycleIntent(action: "Literal['ensure_mismatch', 'retire_mismatch', 'refine_scope']", lifecycle: 'ReportingIssueLifecycle', scope: 'ReportingStatusScope')

Instance variables

var action : Literal['ensure_mismatch', 'retire_mismatch', 'refine_scope']
var lifecycle : ReportingIssueLifecycle
var scope : ReportingStatusScope
class StatusProjectionInput (snapshot: ReportingStatusSnapshot,
scope: ReportingStatusScope,
escalation: ReportingDeliveryEscalation | None = None,
delivery_config_ids: tuple[str, ...] = (),
media_buy_ids: tuple[str, ...] = (),
feed_purposes: tuple[str, ...] = (),
period_start: datetime | None = None,
period_end: datetime | None = None,
reconciliation: tuple[ReportingDeliveryRecord, ...] | None = None,
consumer_status_enabled: bool = True)
Expand source code
@dataclass(frozen=True)
class StatusProjectionInput:
    snapshot: ReportingStatusSnapshot
    scope: ReportingStatusScope
    escalation: ReportingDeliveryEscalation | None = None
    delivery_config_ids: tuple[str, ...] = ()
    media_buy_ids: tuple[str, ...] = ()
    feed_purposes: tuple[str, ...] = ()
    period_start: datetime | None = None
    period_end: datetime | None = None
    # None selects the immutable legacy C representation. Versioned callers
    # pass the complete captured account history, even when it is empty.
    reconciliation: tuple[ReportingDeliveryRecord, ...] | None = None
    consumer_status_enabled: bool = True

StatusProjectionInput(snapshot: 'ReportingStatusSnapshot', scope: 'ReportingStatusScope', escalation: 'ReportingDeliveryEscalation | None' = None, delivery_config_ids: 'tuple[str, …]' = (), media_buy_ids: 'tuple[str, …]' = (), feed_purposes: 'tuple[str, …]' = (), period_start: 'datetime | None' = None, period_end: 'datetime | None' = None, reconciliation: 'tuple[ReportingDeliveryRecord, …] | None' = None, consumer_status_enabled: 'bool' = True)

Instance variables

var consumer_status_enabled : bool
var delivery_config_ids : tuple[str, ...]
var escalation : ReportingDeliveryEscalation | None
var feed_purposes : tuple[str, ...]
var media_buy_ids : tuple[str, ...]
var period_end : datetime.datetime | None
var period_start : datetime.datetime | None
var reconciliation : tuple[ReportingDestinationBinding | ReportingObligationDeliveryRecord | ReportingMaterializationAttempt | ReportingMaterializationRecord | ReportingMaterializationCheck | ReportingRevisionReceiptRecord | ReportingAdjustmentReceiptRecord, ...] | None
var scope : ReportingStatusScope
var snapshot : ReportingStatusSnapshot
class StatusProjectionResult (scope: ReportingStatusScope,
health: ReportingHealth,
issues: tuple[ReportingIssue, ...],
obligations: tuple[StatusObligationProjection, ...],
configurations: tuple[ReportingConfiguration, ...],
pending_count: int,
next_due_at: datetime | None,
intents: tuple[StatusLifecycleIntent, ...],
fingerprint: str)
Expand source code
@dataclass(frozen=True)
class StatusProjectionResult:
    scope: ReportingStatusScope
    health: ReportingHealth
    issues: tuple[ReportingIssue, ...]
    obligations: tuple[StatusObligationProjection, ...]
    configurations: tuple[ReportingConfiguration, ...]
    pending_count: int
    next_due_at: datetime | None
    intents: tuple[StatusLifecycleIntent, ...]
    fingerprint: str

    @property
    def publishable(self) -> bool:
        return not self.intents and (
            any(i.severity == self.health for i in self.issues)
            if self.health in {"delayed", "action_required"}
            else not self.issues
        )

    @property
    def issue_ids(self) -> tuple[str, ...]:
        return tuple(sorted({issue.issue_id for issue in self.issues}))[:16]

    def canonical(self) -> dict[str, Any]:
        return {
            "health": self.health,
            "issues": [issue.to_wire() for issue in self.issues],
        }

StatusProjectionResult(scope: 'ReportingStatusScope', health: 'ReportingHealth', issues: 'tuple[ReportingIssue, …]', obligations: 'tuple[StatusObligationProjection, …]', configurations: 'tuple[ReportingConfiguration, …]', pending_count: 'int', next_due_at: 'datetime | None', intents: 'tuple[StatusLifecycleIntent, …]', fingerprint: 'str')

Instance variables

var configurations : tuple[ReportingConfiguration, ...]
var fingerprint : str
var health : Literal['healthy', 'waiting', 'delayed', 'action_required', 'complete']
var intents : tuple[StatusLifecycleIntent, ...]
prop issue_ids : tuple[str, ...]
Expand source code
@property
def issue_ids(self) -> tuple[str, ...]:
    return tuple(sorted({issue.issue_id for issue in self.issues}))[:16]
var issues : tuple[ReportingIssue, ...]
var next_due_at : datetime.datetime | None
var obligations : tuple[StatusObligationProjection, ...]
var pending_count : int
prop publishable : bool
Expand source code
@property
def publishable(self) -> bool:
    return not self.intents and (
        any(i.severity == self.health for i in self.issues)
        if self.health in {"delayed", "action_required"}
        else not self.issues
    )
var scope : ReportingStatusScope

Methods

def canonical(self) ‑> dict[str, typing.Any]
Expand source code
def canonical(self) -> dict[str, Any]:
    return {
        "health": self.health,
        "issues": [issue.to_wire() for issue in self.issues],
    }
class WorkerTurn (leased: LeasedConfiguration | None = None,
obligations_committed: list[str] = <factory>,
revisions_committed: list[str] = <factory>,
slices_failed: list[str] = <factory>,
escalated: list[str] = <factory>,
earliest_retry_at: datetime | None = None)
Expand source code
@dataclass
class WorkerTurn:
    """What one turn of the worker actually did.

    Returned rather than logged-and-forgotten so a supervisor can decide
    whether to run again immediately or back off, and so tests can assert on
    the loop without scraping logs.
    """

    leased: LeasedConfiguration | None = None
    obligations_committed: list[str] = field(default_factory=list)
    revisions_committed: list[str] = field(default_factory=list)
    slices_failed: list[str] = field(default_factory=list)
    escalated: list[str] = field(default_factory=list)
    earliest_retry_at: datetime | None = None
    _retry_keys_by_obligation: dict[str, tuple[str, str, str]] = field(
        default_factory=dict, repr=False, compare=False
    )
    _retry_entries: dict[str, RetryScheduleEntry] = field(
        default_factory=dict, repr=False, compare=False
    )

    @property
    def did_work(self) -> bool:
        return bool(
            self.obligations_committed
            or self.revisions_committed
            or self.slices_failed
            or self.escalated
        )

What one turn of the worker actually did.

Returned rather than logged-and-forgotten so a supervisor can decide whether to run again immediately or back off, and so tests can assert on the loop without scraping logs.

Instance variables

prop did_work : bool
Expand source code
@property
def did_work(self) -> bool:
    return bool(
        self.obligations_committed
        or self.revisions_committed
        or self.slices_failed
        or self.escalated
    )
var earliest_retry_at : datetime.datetime | None
var escalated : list[str]
var leased : LeasedConfiguration | None
var obligations_committed : list[str]
var revisions_committed : list[str]
var slices_failed : list[str]