Skip to content

feat(reporting-storage): deliver reporting subscriptions through the PostgreSQL production store - #1734

Merged
bokelley merged 1 commit into
nextfrom
claude/issue-1695-managed-pg
Oct 31, 2026
Merged

bokelley merged 1 commit into
nextfrom
claude/issue-1695-managed-pg

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Slice 4b of #1695, stacked on #1733 (4a2): managed delivery of reporting subscriptions through the PostgreSQL production store. With this, the memory and PostgreSQL production stores both serve subscriptions with bind_managed_generation, with or without status notifications.

Store

  • PgReportingProductionStore implements the owed-set contract the memory store gained in feat(reporting): deliver reporting subscriptions through production managed delivery #1733:
    • admit_subscription_generation
    • subscription_generation_admitted
    • commit_subscription_obligation
    • production_owed_sets
  • The state lives in two private tables:
    • adcp_reporting_production_owed_sets holds the enrollment: producer key and frozen managed destination binding.
    • adcp_reporting_production_owed_constituents holds each owed obligation's frozen constituents. They are written in the obligation's transaction and never rewritten.
  • Planning, dispatch (producer_constituents) and the materializer context read the owed set. An owed set has no generation-wide source binding.
  • Activation re-enrolls owed sets from their bindings.

The binding identity question

A destination binding's record_id is the hash of its full generation key, offering_id included, in Python (record_identity). The SQL validator hashed only (account, consumer, id, version). So two offerings could never share an identity, but PostgreSQL would have rejected every subscription binding. The validator and every binding lookup now carry the offering. That covers the validator, the evidence check, the reference trigger, the materializer scope, discovery and the obligation trigger.

Shared tables

  • Reconciliation records, materializer discovery and status checkpoints record the generation's offering, and delivery_pg writes it.
  • create_schema drops feat(reporting): hold reporting-subscription owed sets in the PostgreSQL ledger #1729's account-only CHECK on those three tables and widens the discovery key to include offering_id. The upgrade is in place, lossless and idempotent, for schemas created between merges.
    • Earlier workers refuse the upgraded schema, so stop them first. docs/reporting-ledger-migration.md says so.
  • Production generations keep their account-only CHECK.

One offering per subscription version

Status checkpoints are still keyed per (account, consumer, id, version). Admission therefore refuses a second offering of one subscription version for the same account and owner, in both stores, with reporting_production_offering_conflict. That fails the subscription's planning; nothing is written.

Under that rule, the production store lifts the "status notifications refuse owed sets" check. PgReportingLedgerStore outside production keeps it. Widening the status checkpoint and event keys is a follow-up.

Pins

The required-schema manifests are regenerated: the materializer, outbox, status and production manifests. So are the pinned catalog counts:

  • parent manifest 774 → 772;
  • materializer 189 → 188;
  • catalog scope 801 → 799.

Tests

  • test_reporting_subscription_production.py now runs every scenario against memory and PostgreSQL, adding:

    • delivery with status notifications on;
    • the second-offering refusal.
  • test_reporting_subscription_ledger_pg.py: an account-only managed-delivery schema is upgraded idempotently.

  • Local runs:

    • the reporting conformance suite against PostgreSQL 16, excluding the installed, packaging, rolling and interop jobs, which need more disk than this sandbox has; CI runs them;
    • the reporting subscription and production unit suites;
    • ruff and mypy.

    Failures under -n 2 were lease and lock-timing contention: production advisory locks are database-wide, so parallel workers contend. All 61 of them pass serially.

Refs #1695

🤖 Generated with Claude Code

…PostgreSQL production store

PgReportingProductionStore now serves managed subscription delivery, as the
memory store does since the previous change. Both stores keep one admitted
owed set per generation, and each owed obligation's frozen constituents.

- PostgreSQL keeps the enrollment (producer and frozen destination binding)
  and the constituents in two private tables,
  adcp_reporting_production_owed_sets and
  adcp_reporting_production_owed_constituents. The constituents are
  written in the obligation's transaction and never rewritten.
- Planning, dispatch and the materializer context read the owed set; an
  owed set has no generation-wide source binding. Activation re-enrolls
  owed sets from their bindings.
- Reconciliation records, materializer discovery and status checkpoints
  record the generation's offering. A destination binding's record_id
  derives from its full generation key, so offerings no longer share an
  identity: the SQL validator previously hashed only (account, consumer,
  id, version) and would have rejected every subscription binding. Binding
  lookups in the validators and the materializer scope carry the offering.
- create_schema drops the account-only CHECKs on those three tables and
  widens the materializer discovery key to include offering_id, in place
  and idempotently, for schemas created by the previous release. Earlier
  workers refuse the upgraded schema: stop them first. Production
  generations keep their account-only CHECK, and the obligation trigger
  joins them on the offering too.
- Status checkpoints are still keyed per (account, consumer, id,
  version), so both stores refuse a second offering of one subscription
  version for the same account and owner with
  reporting_production_offering_conflict. Under that rule the PostgreSQL
  production store serves owed sets with status notifications enabled.
- The required-schema manifests and pinned catalog counts are regenerated.

The managed-delivery tests run against both stores, with and without
status notifications, and cover the second-offering refusal and the
schema upgrade.

Refs #1695

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — Slice 4b extends managed reporting-subscription delivery to the PostgreSQL production store.

The reviewer returned a clean approve with no blocking or Medium findings. Walking the decision table top-down:

  • Rows 1 (critical/high): no findings — not triggered.
  • Row 2 (gated_paths): false — not triggered.
  • Rows 3–5 (high_risk deletions/modifications + medium): high_risk is false — not triggered.
  • Row 6 (sticky escalate): no prior decision — not triggered.
  • Row 7 (no-auto-approve team): no team match — not triggered.
  • Row 8 (≥3 medium findings): zero medium findings — not triggered.
  • Row 9: approve.

What was checked: the load-bearing SQL change widens the generation key 4→5 columns (offering_id) and the destination_binding identity hash conditionally appends offering_id only when non-empty, matching Python record_identity/ledger_asdict exactly — account-level hashes (offering_id='') stay byte-stable in canonical sorted-key order, so existing records are unaffected and subscription bindings stop being rejected. _check_context fail-closes on NULL producer_key; commit_subscription_obligation freezes constituents via ON CONFLICT DO NOTHING plus a read-back immutability guard; schema upgrade (DROP account-only CHECK, widen discovery PK) is guarded, idempotent and lossless. The feat(reporting-storage) semver signal is correct — additive, no public-surface break. Tests cover both stores, notifications on/off, second-offering refusal, and idempotent schema upgrade.

Base automatically changed from claude/issue-1695-managed-production to next October 31, 2026 14:24
@bokelley
bokelley merged commit 333772f into next Oct 31, 2026
4 checks passed
@bokelley
bokelley deleted the claude/issue-1695-managed-pg branch October 31, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant