Skip to content

feat(reporting): add durable seller-triggered repair for buyer-reported snapshot mismatches #1343

Open

Description

@rachitm022

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 official revision; 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

  1. Three buyer-reported daily mismatches outside the settling window can be enqueued and produce three corrected revisions.
  2. Retrying the same enqueue request does not create duplicate jobs or duplicate repair acquisitions.
  3. A stale expected_revision_id is rejected or reconciled explicitly rather than superseding an unexpected newer revision.
  4. The bad revisions remain readable and unchanged after repair.
  5. Currency/schema validation failure publishes no revision and retains an actionable repair outcome.
  6. A worker restart or lease expiry does not lose or duplicate the repair.
  7. The corrected revisions appear in changes_after results and remain readable through exact revision reads.
  8. The existing consumer mismatch remains open until the buyer submits a superseding agreeing status.
  9. 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

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

    @bokelley
    Contributor

    Triage

    Classification: Feature request
    Bucket(s): client (client label absent — gap noted)
    Status: ready-to-implement

    What the experts said:

    • ad-tech-protocol-expert: Protocol-correct; idempotency key must anchor to superseded_revision_id to prevent multi-buyer duplication; provisional_until on the repair revision needs explicit definition.
    • code-reviewer: Non-breaking only if RepairQueueStore is a new optional Protocol (not extending ReportingLedgerStore); settling-window bypass in producer.py is the riskiest seam; 2-PR rollout recommended.

    My take: Both experts agree on proceed after three design questions are resolved (idempotency key, provisional_until semantics, adapter-first vs. production path). No /triage execute — stopping at implementation brief.

    • Resolve before coding: (1) idempotency key (obligation_id, superseded_revision_id) vs. causing_reporting_status_id; (2) provisional_until value on the repair revision; (3) run_worker() vs. production path target.
    • New RepairQueueStore optional Protocol + RepairQueueEntry in ledger/store.py; memory + Postgres impls + SQL DDL.
    • New run_repair() in ledger/producer.py to bypass the settling-window guard; enqueue_reporting_repair() + _run_worker() drain in reporting/service.py.
    • Conformance tests (Memory + Postgres): AC3 (stale revision check atomic), AC6 (lease expiry safe), AC8 (buyer mismatch stays open post-repair).

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


    Generated by Claude Code

  3. added
    pythonPull requests that update python code
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions