Skip to content

reporting: replace an all-empty pre-ownership ledger without the archive ceremony #1439

Closed

Description

@bokelley

Summary

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

  1. 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.
  2. 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.

Activity

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

    @bokelley
    ContributorAuthor

    Triage

    Classification: Feature request
    Bucket(s): client (reporting/migration), docs
    Status: ready-to-implement
    Milestone: omitted

    What the experts said:

    • 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

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


    Generated by Claude Code

  3. added
    enhancementNew feature or request
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 7, 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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions