Repository navigation
Description
Summary
Period retirement deletes a period's consumer statements and obligation, but it leaves that period's open or acknowledged reporting_issue_lifecycle occurrences open. Nothing can retire them afterwards. As a result:
- the issue observer never receives a
resolvedevent for them; - the escalation sweep keeps treating them as live, and after fix(reporting): issue-event retention prune re-escalates still-open occurrences every 7 days #1885's prune deletes their delivered
escalatedevent, it re-escalates them every retention period, forever.
This is the sibling of #1885. The TypeScript SDK found and fixed the same gap in its port (adcontextprotocol/adcp-client#3654 follow-up), where retirement now resolves the period's open occurrences in the retirement transaction.
Where (next @ 74bbe6f)
period_retirement.py_delete_records(L447-L501) deletesreporting_consumer_statuses, the period's revisions and thereporting_obligationsrow. It never touchesreporting_issue_lifecycleand records no issue event.status_projection.pylifecycle_intents(L227-L336) retires a mismatch occurrence only while iteratingsnapshot.statuses("retire_mismatch"at L290). Once retirement has deleted the period's statements, no settle can ever reach that occurrence again.issue_events.pysweep_escalations(L204+) selectsissue_state IN ('open', 'acknowledged')rows with noescalatedevent. A retired period's occurrence qualifies again as soon as fix(reporting): issue-event retention prune re-escalates still-open occurrences every 7 days #1885's prune removes the deliveredescalatedrow.
Reproduction
- A mismatch opens and escalates, and both events are delivered.
- Retention retires the period: its statements and obligation are deleted, and the lifecycle row stays
open. - No
resolvedis ever recorded, so the seller's alert never closes. - Seven days later, prune deletes the delivered
escalated, and the next sweep records and delivers it again, with the sameevent_id. This repeats indefinitely (fix(reporting): issue-event retention prune re-escalates still-open occurrences every 7 days #1885).
Suggested fix
Inside the retirement transaction, retire the period's live mismatch occurrences: set issue_state = 'resolved' and retired_at, and call record_issue_transition_on so the outbox gets resolved with its usual same-transaction guarantee. Keep the outbox rows, since undelivered events must still go out. The resolved event renders issue=None. The TypeScript fix does exactly this with its existing per-obligation settle.
Python keys occurrences per chain (issue_key), not per obligation. So the retirement needs to resolve the occurrences whose scope (issue_scopes) names the retired obligation, or whose chain identity matches the retired period.
Add a test: an escalated, delivered occurrence of a period retired by retire_expired_periods gets exactly one resolved, is never re-escalated, and is eventually pruned.
Activity