Conversation
| @property | ||
| def generation_key(self) -> tuple[str, int]: | ||
| return (self.delivery_config_id, self.delivery_config_version) | ||
| def generation_key(self) -> ReportingConfigurationGenerationKey: |
There was a problem hiding this comment.
MUST FIX: ReportingConfiguration.generation_key changes its public return type from tuple[str, int] to ReportingConfigurationGenerationKey. That is a breaking type-signature change to a public export in the adcp.* namespace — any adopter doing config_id, version = config.generation_key breaks. The commit is fix(reporting): scope configuration generations by account with no ! and no BREAKING CHANGE: footer. release-please reads the prefix, not the diff; the break ships without a major signal. Retag fix!: or add a BREAKING CHANGE: footer. Migration note is present (docs/reporting-ledger-migration.md), so only the semver signal is missing.
Context, not absolution: generation_key was introduced in #1153 within the 8.0.0-beta line and has never shipped in a stable release, so the practical blast radius is beta adopters only — the arbiter can weigh that.
There was a problem hiding this comment.
Ladon verdict: Request changes
Request changes — 1 blocking finding.
Blocking findings
- src/adcp/reporting/ledger/models.py:338 —
ReportingConfiguration.generation_keychanges its public return type fromtuple[str, int]to a dataclass under a non-breakingfix:commit prefix, with no!/BREAKING CHANGE:footer and no migration note. Per the repo's mandatory semver-signal gate, a public-surface type-signature change shipped under a non-breaking conventional-commit prefix is ahighfinding: release-please would cut a minor fromfix:, shipping the break without a major. Addfix!:/feat!:or aBREAKING CHANGE:footer plus a migration note.
The core account-qualifying work is well-executed — generation identity is consistently threaded across both stores (PK, ON CONFLICT targets, obligation period key, lease/release with expiry match, revision supersession, status/issue joins), the beta.15 migration SQL is fail-closed and idempotent, and new branches are covered by memory + Postgres conformance tests. No medium findings. Only the semver-signal blocker needs resolution.
Blocking findings
- src/adcp/reporting/ledger/models.py:338 — generation_key public return type changed from tuple[str, int] to a dataclass under a non-breaking fix: prefix with no !/BREAKING CHANGE footer or migration note (semver-signal gate: high)
InMemoryReportingLedgerStore.lease_period_close handed back the first leasable generation in insertion order, and release_period_close puts that generation straight back into the free pool. ReportingProducer.run_worker releases in a finally, so a worker loop re-leased the same generation on every turn and never closed a period for any other account. The SQL store orders by lease_expires_at NULLS FIRST, and an update to that indexed column sends a released row to the back of the queue, so the two stores only diverged once two accounts could hold the same delivery_config_id -- which is what this branch enables. Rank leasable generations the way the SQL store does, unheld before expired and oldest expiry first, then break the tie by whichever generation went longest without a turn. The shared memory/PostgreSQL suite now asserts that a worker releasing every turn reaches all three accounts; it failed on memory and passed on PostgreSQL before this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — delta review of fair-queuing fix is clean, no blocking findings.
This is a subsequent review (prior outcome: request-changes, with no recorded findings). The current run reviewed the in-memory store's lease_period_close fair-queuing fix (store.py L1109-1147) plus a conformance test running against both memory and postgres stores. The ranking mirrors the SQL ORDER BY lease_expires_at NULLS FIRST and adds a turn-based tiebreak that closes the account-starvation window when two accounts share a delivery_config_id. Store parity holds and it was validated on real PG (163 passed).
Decision-table walk:
- Row 1 (critical/high): no findings — skip.
- Row 2 (gated_paths + not APPROVED):
gated_pathsis false — skip. (Note:review_decisionis CHANGES_REQUESTED, but that gate only applies whengated_pathsis true.) - Rows 3-5 (high-risk deletions/modified + medium):
high_riskis false — skip. - Row 6 (sticky escalate): prior was request-changes, not escalate — skip.
- Row 7 (no-auto-approve team): no team match — skip.
- Row 8 (≥3 medium): zero medium findings — skip.
- Row 9: approve.
No author-team gate and no gated-paths gate apply. Net improvement over the prior request-changes with a clean current run.
Superseded by Ladon approval of 6c74a45.
Two authorized accounts accepting
daily@1previously shared configuration storage and lease identity. This change introduces the frozen publicReportingConfigurationGenerationKey(account_id, delivery_config_id, delivery_config_version)and uses it across configuration maps, obligation lookup, worker resolution, leases, and status/issue joins in both stores.PostgreSQL conflicts and lease updates select the complete account-qualified generation. Concurrent configuration writes check the retained winner's digest and reject changed content. Releases also match expiry so an expired handle cannot release a replacement held under the same worker ID. Reusing a global obligation ID for another logical period is rejected consistently.
Closes #1169
Migration and compatibility
create_schema()runs bootstrap and the bundledreporting_ledger_account_generations.sqlin one transaction under a shared schema advisory lock. The standalone migration is also atomic in autocommit mode.CASCADEor data deletion.generation_keyintentionally stops returning a two-tuple. Use its named fields. Existing constructors and low-level store method arguments remain compatible. Persisted consumer-status/obligation/issue identities retain their serialization.docs/reporting-ledger-migration.mdon this branch.Fair lease scheduling
Independent review found that the in-memory store always reacquired the first released generation, starving later accounts while PostgreSQL rotated fairly. The reference store now ranks unheld before expired leases, oldest expiry first, then least recently leased. A shared memory/PostgreSQL regression proves three accounts receive a 4/4/4 split across 12 worker turns.
Validation
Residual risks
ACCESS EXCLUSIVElock; production-sized duration is not benchmarked. Adopter-added dependent schemas require an adopter-owned migration.reporting_status_idreuse fails closed with different error codes in memory and PostgreSQL; this behavior predates the PR.list_configurationsinsertion order differs from PostgreSQL's sorted order; callers match by frozen key, so this is functionally inert.Open workspace in Conductor