You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
reporting: replace an all-empty pre-ownership ledger without the archive ceremony #1439
require_owned_schema_or_empty refuses to start whenever reporting_configurations exists without consumer_id, even if every legacy reporting table is empty. The only supported path is then migrate_legacy_reporting(archive_schema=..., workers_stopped=True). That creates an archive schema, moves empty tables into it, installs write-blocking triggers, and requires a stopped-worker maintenance window.
Many 8.x adopters turned reporting on but never admitted a configuration. That's the common case for a seller that shipped the capability before any buyer used it. For them the archive is pure ceremony, and the stopped-worker requirement turns a routine deploy into a maintenance event.
The Embedded Sales Agent hit this: 29 legacy tables, all with zero rows in production. We wrote our own Alembic migration instead. It locks every reporting_* / adcp_reporting_* table, refuses if any holds a row, and otherwise drops the tables, sequences and functions so create_schema() recreates the owned schema on start.
Ask
Treat an all-empty legacy ledger as empty. Either require_owned_schema_or_empty/create_schema replaces it in place under the schema advisory lock, or a supported replace_empty_legacy_reporting(connection) does it, refusing if any table holds a row.
In docs/reporting-caller-ownership-migration.md, add a short section on rolling deploys: what old (8.x) pods do once the owned schema exists. From the DDL we expect their writes to fail on consumer_id NOT NULL rather than write unowned rows. If that holds, please state it as a guarantee.
code-reviewer: Both items are worth doing; the workers_stopped-free framing is misleading — ACCESS EXCLUSIVE table locks are still required before the emptiness check (advisory lock alone doesn't block concurrent INSERTs under READ COMMITTED), and the docs addition must reconcile with the existing "mixed-version unsupported" statement.
dx-expert: Real pain point; feature is well-motivated — but require_owned_schema_or_empty's error message must surface both paths or the new function is invisible to the operators it helps, and the rolling-deploy section must verify the consumer_id NOT NULL constraint semantics (fail-fast vs. silent corruption) before shipping.
My take: Both items are ready to implement. The "no maintenance window" promise holds because the database enforces effective quiesce via ACCESS EXCLUSIVE table locks — it's a UX improvement for the empty case, not a correctness shortcut. The docs section is high-value and the only prerequisite is verifying the DDL guarantee and reconciling one sentence in the existing guide.
Implementation brief:
Item 1 — replace_empty_legacy_reporting(connection) in src/adcp/reporting/migration.py:
Acquire pg_advisory_xact_lock (same key as migrate_legacy_reporting)
In the same transaction, acquire ACCESS EXCLUSIVE on every reporting_* / adcp_reporting_* table (same pattern as lines 126–131 of migration.py)
After locks are held, count rows in each table; raise ReportingOwnershipMigrationError if any table has > 0 rows
Drop all reporting tables, sequences, and functions (no archive schema, no triggers, no workers_stopped assertion needed — the locks are the quiesce)
Add to __all__ alongside existing names; do not export from adcp/reporting/__init__.py
Update the require_owned_schema_or_empty error message (line 76–78) to surface both paths, e.g.: "If all legacy tables are empty, call replace_empty_legacy_reporting. Otherwise stop all reporting workers and run migrate_legacy_reporting."
Tests in tests/conformance/reporting/test_reporting_caller_ownership.py: success (all empty), refusal (any table non-empty), and create_schema succeeds immediately after
Item 2 — Rolling-deploy section in docs/reporting-caller-ownership-migration.md:
Check src/adcp/reporting/ledger/reporting_caller_ownership.sql to confirm consumer_id NOT NULL ships in the DDL (not just a check constraint)
Reconcile with the "mixed-version reporting processes are unsupported" statement — narrow it to: unsupported for non-empty ledger transitions; the new NOT NULL constraint makes the failure mode for 8.x pods loud and bounded (constraint violation on write, not silent data corruption)
Document the safe rolling-deploy sequence: new schema first, then pods — old 8.x pods hit consumer_id NOT NULL violation on any reporting write and fail fast; no unowned rows are silently written
Activity
bokelley commentedon Oct 7, 2026
Triage
Classification: Feature request
Bucket(s): client (reporting/migration), docs
Status: ready-to-implement
Milestone: omitted
What the experts said:
workers_stopped-free framing is misleading —ACCESS EXCLUSIVEtable locks are still required before the emptiness check (advisory lock alone doesn't block concurrent INSERTs under READ COMMITTED), and the docs addition must reconcile with the existing "mixed-version unsupported" statement.require_owned_schema_or_empty's error message must surface both paths or the new function is invisible to the operators it helps, and the rolling-deploy section must verify theconsumer_id NOT NULLconstraint semantics (fail-fast vs. silent corruption) before shipping.My take: Both items are ready to implement. The "no maintenance window" promise holds because the database enforces effective quiesce via
ACCESS EXCLUSIVEtable locks — it's a UX improvement for the empty case, not a correctness shortcut. The docs section is high-value and the only prerequisite is verifying the DDL guarantee and reconciling one sentence in the existing guide.Implementation brief:
Item 1 —
replace_empty_legacy_reporting(connection)insrc/adcp/reporting/migration.py:pg_advisory_xact_lock(same key asmigrate_legacy_reporting)ACCESS EXCLUSIVEon everyreporting_*/adcp_reporting_*table (same pattern as lines 126–131 ofmigration.py)ReportingOwnershipMigrationErrorif any table has > 0 rowsworkers_stoppedassertion needed — the locks are the quiesce)__all__alongside existing names; do not export fromadcp/reporting/__init__.pyrequire_owned_schema_or_emptyerror message (line 76–78) to surface both paths, e.g.: "If all legacy tables are empty, callreplace_empty_legacy_reporting. Otherwise stop all reporting workers and runmigrate_legacy_reporting."tests/conformance/reporting/test_reporting_caller_ownership.py: success (all empty), refusal (any table non-empty), andcreate_schemasucceeds immediately afterItem 2 — Rolling-deploy section in
docs/reporting-caller-ownership-migration.md:src/adcp/reporting/ledger/reporting_caller_ownership.sqlto confirmconsumer_id NOT NULLships in the DDL (not just a check constraint)consumer_id NOT NULLviolation on any reporting write and fail fast; no unowned rows are silently writtenTriaged by Claude Code. Session: https://claude.ai/code/session_01MrsLsYJQjbFqFy3yDZBZ57
Generated by Claude Code