Repository navigation
feat(reporting): add durable seller-triggered repair for buyer-reported snapshot mismatches #1343
Description
Summary
Add a durable seller-side API for forcing a fresh acquisition of one snapshot reporting obligation after its normal restatement window has closed.
This is needed when a buyer reports an exact content_mismatch (for example currency_mismatch or schema_nonconformant), the seller fixes the underlying production bug, but the affected period is no longer eligible for the producer's scheduled settling reads.
Problem
The consumer-status loop identifies the exact caller/account, configuration generation, period, obligation, bad revision, and causing reporting_status_id. The SDK correctly records the mismatch and projects CONSUMER_STATUS_MISMATCH.
However, once the obligation's provisional_until / restatement window has passed, ReliableReportingService exposes no supported way for the seller adopter to schedule a fresh source acquisition. The adopter would have to manipulate internal restatement checkpoints or ledger state, which is unsafe and couples it to SDK internals.
As a result, a buyer-reported mismatch can remain action_required even after the source bug has been fixed because the seller cannot publish a corrected immutable revision.
Related: #1258 would notify the adopter when the mismatch opens. This issue covers the separate repair operation after the adopter has diagnosed and fixed the cause.
Scope
This issue only covers obligations identified by buyer-reported mismatches. It does not propose a general incident scanner for finding unreported affected accounts or periods.
Proposed API
The exact name is open for discussion; conceptually:
await service.enqueue_reporting_repair(
account_id=ledger_account_id,
reporting_obligation_id=statement.reporting_obligation_id,
causing_reporting_status_id=statement.reporting_status_id,
expected_revision_id=statement.reporting_revision_id,
reason=statement.mismatch_code,
)This is an internal seller/adopter API, not a new AdCP buyer task. The SDK should not automatically replay data merely because a buyer submitted a mismatch. The adopter calls it after validating the incident and fixing the underlying cause.
Required semantics
- Validate that the account owns the obligation and that the causing consumer status refers to that exact chain.
- Support snapshot/provisional obligations. Reject attempts to rewrite an
officialrevision; official corrections continue to use the protocol's adjustment model. - Persist one idempotent repair job per
(account_id, reporting_obligation_id, causing_reporting_status_id). - Allow the worker to lease and execute the job safely across process restarts and multiple replicas.
- Force one fresh source acquisition even when the normal restatement window has expired, using the obligation's frozen configuration, period, scope, metrics, schema, and currency.
- Run the ordinary source, currency, schema, coverage, and revision-binding validation. A failed validation must not publish a revision.
- On success, commit a new immutable snapshot revision that supersedes the current snapshot. Never mutate or delete the bad revision.
- Use the existing retry/backoff behavior for retryable source failures, with an inspectable terminal outcome for non-retryable failures.
- Expose the new revision through the ordinary checkpointed
get_reporting_status(view="periods", changes_after=...)ledger. - Do not resolve the buyer's mismatch merely because the repair job succeeded. The issue remains until the buyer validates the corrected revision and supersedes its status with
received, or the existing waiver rules apply.
For multiple affected periods, the adopter enqueues one repair per obligation. Each period therefore receives its own corrected revision and supersession lineage.
Acceptance criteria
- Three buyer-reported daily mismatches outside the settling window can be enqueued and produce three corrected revisions.
- Retrying the same enqueue request does not create duplicate jobs or duplicate repair acquisitions.
- A stale
expected_revision_idis rejected or reconciled explicitly rather than superseding an unexpected newer revision. - The bad revisions remain readable and unchanged after repair.
- Currency/schema validation failure publishes no revision and retains an actionable repair outcome.
- A worker restart or lease expiry does not lose or duplicate the repair.
- The corrected revisions appear in
changes_afterresults and remain readable through exact revision reads. - The existing consumer mismatch remains open until the buyer submits a superseding agreeing status.
- Memory and PostgreSQL stores have equivalent conformance coverage.
Non-goals
- Automatically trusting every buyer mismatch and immediately querying historical source data.
- Discovering affected accounts that have not submitted consumer statuses.
- Rewriting official revisions or historical ledger records.
Activity
bokelley commentedon Oct 2, 2026
Triage
Classification: Feature request
Bucket(s): client (
clientlabel absent — gap noted)Status: ready-to-implement
What the experts said:
superseded_revision_idto prevent multi-buyer duplication;provisional_untilon the repair revision needs explicit definition.RepairQueueStoreis a new optional Protocol (not extendingReportingLedgerStore); settling-window bypass inproducer.pyis the riskiest seam; 2-PR rollout recommended.My take: Both experts agree on proceed after three design questions are resolved (idempotency key,
provisional_untilsemantics, adapter-first vs. production path). No/triage execute— stopping at implementation brief.(obligation_id, superseded_revision_id)vs.causing_reporting_status_id; (2)provisional_untilvalue on the repair revision; (3)run_worker()vs. production path target.RepairQueueStoreoptional Protocol +RepairQueueEntryinledger/store.py; memory + Postgres impls + SQL DDL.run_repair()inledger/producer.pyto bypass the settling-window guard;enqueue_reporting_repair()+_run_worker()drain inreporting/service.py.Triaged by Claude Code. Session: https://claude.ai/code/session_01JwT61nphPo9oVqvi4SGuKN
Generated by Claude Code