Skip to content

9.0 adoption follow-ups: sync empty-ledger replacement, owned-ledger rollback, declaration-to-Format publishing #1449

Open

Description

@bokelley

Three things the Embedded Sales Agent (scope3data/embedded-sales-agent) hit while adopting 9.0.0b2. Most of #1441 landed in b2; thank you. These are what came up using it.

1. replace_empty_legacy_reporting only takes an async psycopg connection

Most adopters run schema changes through a sync migration framework (Alembic on SQLAlchemy). To call the replacement from an Alembic revision, ESA opens a second, async psycopg connection and runs it with asyncio.run(...). That works, but the drop no longer commits atomically with the framework's version bump. If the revision fails after the replacement, the drop is already committed (harmless here, since the next run sees no legacy ledger, but it isn't what an operator expects from one migration). The asyncio.run also breaks if a migration runner is ever invoked from inside an event loop.

Ask: a sync variant that accepts a sync psycopg connection or a SQLAlchemy Connection, so the replacement runs inside the caller's transaction. Or at least a documented recipe for Alembic.

2. No rollback story for the owned ledger

Once create_schema() has built the caller-owned ledger, an 8.x image can't run against it. So rolling back past the 9.0 upgrade needs the owned ledger removed first. ESA's downgrade does this itself: it locks the tables, refuses if any holds a row, and drops them. That duplicates the care replace_empty_legacy_reporting already takes, in the other direction.

Ask: either a replace_empty_owned_reporting(connection) (same guarantees, the opposite shape) or a short rollback section in docs/reporting-caller-ownership-migration.md saying what an operator must do before deploying an 8.x image again.

3. Validating a ProductFormatDeclaration, then publishing a Format

ProductFormatDeclaration is now the strict authoring union, while Product.format_options is list[Format]. A seller converting stored declarations to products wants both: validate each option strictly, then publish it. Two obvious paths go wrong:

  • Passing the validated branch model to Product fails, because a ProductFormatDeclaration1 is not a Format.
  • Passing its model_dump(exclude_none=True) adds every branch default (asset_source, buyer_asset_acceptance, deprecated, experimental, …) to the published format, which changes the wire.

ESA now validates with validate_union(ProductFormatDeclaration, option) and publishes the original dict. Ask: document that pattern in the types guide, or add a helper that validates a declaration and returns the Format built from the caller's own fields (for example a model_dump(exclude_unset=True)-based projection).

Activity

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

    @bokelley
    ContributorAuthor

    Triage

    Classification: Feature request (items 1–2); docs/ergonomics (item 3)
    Bucket(s): client, docs
    Status: ready-to-implement (items 1–2); deferred — blocked-on #1447 (item 3)

    What the experts said:

    • code-reviewer: items 1 & 2 are non-breaking additions; sync psycopg 3 variant is cleanest for item 1; replace_empty_owned_reporting must mirror the advisory-lock → ACCESS EXCLUSIVE → row-guard → drop chain; guard adcp_reporting_ownership_archives/_backfills from the drop.
    • dx-expert: a recipe is insufficient — the reference Alembic env already occupies the event loop, so asyncio.run inside a revision raises at runtime; a function is required for item 2 because docs-only leaves operators writing unsafe DDL without the advisory lock or dependency guard.

    My take: Items 1 & 2 have expert consensus and are non-breaking additions. Item 3 resolves once #1447 merges — ProductFormatDeclaration will subclass Format, making model_dump(exclude_unset=True) correct by construction; the pattern note belongs in #1447's already-updated migration docs.

    Implementation scope (items 1–2):

    • Add replace_empty_legacy_reporting_sync(connection) in src/adcp/reporting/migration.py — sync psycopg 3, removes double-await, same isolation check and autocommit=True constraint; add Alembic usage note to docs/reporting-caller-ownership-migration.md.
    • Add replace_empty_owned_reporting(connection) — advisory lock → ACCESS EXCLUSIVE all reporting_*/adcp_reporting_* tables → refuse if any row → drop; inverse of ownership-marker check (line 116–119); skip adcp_reporting_ownership_archives/_backfills; add rollback section to docs.
    • Conformance tests in tests/conformance/reporting/test_reporting_empty_legacy_replacement.py (or a parallel file for the rollback path).
    • Both are non-breaking new exports; no existing signatures changed.

    Item 3 — blocked-on #1447. Resurfaces on merge.


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


    Generated by Claude Code

  3. added
    enhancementNew feature or request
    pythonPull requests that update python code
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 8, 2026
  4. bokelley commented on Oct 11, 2026

    @bokelley
    ContributorAuthor

    An update on point 3, after 9.0.0-beta.3. ProductFormatDeclaration is a Format subclass again, so passing it to Product works. But its model_validate now grades params from the schema. Stored declarations written before 9.0 can therefore stop projecting:

    • a slot with required_pixel_ratios but no pixel_ratios (correctly refused by the 3.2 rule);
    • a malformed params bag a seller chose to pass through.

    ESA now publishes stored declarations through the open Format class, which matches what adcp 8 did, and keeps strict grading for authoring. Suggestion for the types guide: say plainly that Format is the lenient read and publish path, and ProductFormatDeclaration is the strict authoring check. Optionally add a non-raising validation_issues(data) so sellers can report data quality without failing a read.

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