Skip to content

fix: Allow multiple accounts per (party, org, instrument) - unblock nightly Demo Reset#2174

Merged
bjcoombs merged 2 commits intodevelopfrom
fix/payg-account-uniqueness
Apr 16, 2026
Merged

fix: Allow multiple accounts per (party, org, instrument) - unblock nightly Demo Reset#2174
bjcoombs merged 2 commits intodevelopfrom
fix/payg-account-uniqueness

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

Summary

Drops the unique partial index idx_account_syndicate_scope_integrity on (party_id, org_party_id, instrument_code) introduced by PRD-022. The constraint was modelled on a betting-syndicate use case (one Alice position per currency in Venture Alpha) but does not generalise to utility billing, where a single customer legitimately holds multiple billing accounts of the same instrument with the same supplier.

Why now

The nightly Demo Reset workflow has failed 25 nights running since the PAYG Energy demo tenant was added on 2026-03-27 (#1963). The fixture seeder creates two GBP accounts per customer for Utilita PAYG billing - one for electricity (PPM-ELEC-CUST00n), one for gas (PPM-GAS-CUST00n) - both with the supplier as org_party_id. The second insert hits the unique index, the server returns AlreadyExists, the seeder cannot find the conflicting account by external_id, and the seed bails out.

Error: seed fixtures: create accounts: create gas billing account for Margaret Thornton:
account reported as existing but not found in listing: external_id="PPM-GAS-CUST001"

The same shape applies beyond gas/electric: multi-site customers (one billing account per premise), multi-product offerings (separate accounts per service line), and aggregated views all require multiple instrument-equivalent accounts under one supplier.

Changes

  • New migration 20260416000001_drop_syndicate_scope_integrity_index.sql drops the index. Includes rationale in the file header.
  • atlas.sum updated via atlas migrate hash.
  • org_scoped_migration_test.go updated:
    • IndexesCreated no longer asserts the dropped index exists.
    • New ScopeIntegrityIndexDropped subtest documents the migration outcome.
    • UniqueConstraintOnPartyOrgInstrumentCode renamed to MultipleAccountsPerPartyOrgInstrumentAllowed and inverted: two GBP accounts for the same (party, org) now succeed; a duplicate account_identification is still rejected.

Safety

  • idx_account_account_identification (UNIQUE on account_identification) still prevents accidental duplicate accounts - the only thing that changes is the (party, org, instrument) triple is no longer constrained to a single row.
  • The two non-unique indexes (idx_account_participant_syndicate, idx_account_syndicate_participants) supporting NFR-1 / NFR-2 syndicate position queries are untouched.
  • No application code change required - the seed-dev createAccountIdempotent flow already handles the legitimate case correctly.

Test plan

  • Atlas migration validates against CockroachDB dev container (already verified locally).
  • TestOrgScopedMigrations_CockroachDB passes (covers index-dropped assertion + multi-service billing case).
  • Nightly Demo Reset goes green after merge + image rebuild + manual workflow_dispatch.

…ervice billing

Drops idx_account_syndicate_scope_integrity, the unique partial index on
(party_id, org_party_id, instrument_code) introduced in PRD-022. The original
constraint modelled a syndicate where each participant holds one position per
currency in a venture, but it does not match utility billing patterns where a
single customer legitimately holds separate billing accounts of the same
instrument with the same supplier (e.g. PPM-ELEC-CUST001 and PPM-GAS-CUST001
under the same supplier party for Margaret Thornton in the PAYG demo).

This has caused the nightly Demo Reset workflow to fail since the PAYG Energy
tenant was added on 2026-03-27 (#1963). With the index removed, account
uniqueness is still enforced by idx_account_account_identification, so the
constraint relaxation does not allow accidental duplication; it only allows
the legitimate multi-service-per-supplier pattern.

Updates the org-scoped migration test to assert the new behaviour (multiple
GBP accounts allowed per party+org, account_identification uniqueness still
enforced).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e0d03e05-749a-4615-977c-a9b5b04de348

📥 Commits

Reviewing files that changed from the base of the PR and between 93f078d and 4c1184c.

📒 Files selected for processing (1)
  • services/current-account/adapters/persistence/org_scoped_migration_test.go
✅ Files skipped from review due to trivial changes (1)
  • services/current-account/adapters/persistence/org_scoped_migration_test.go

📝 Walkthrough

Walkthrough

Adds a migration that drops the idx_account_syndicate_scope_integrity index and updates tests: verifies the index is removed, changes org-scoped uniqueness expectations to allow multiple accounts for the same (party_id, org_party_id, instrument_code), and adds checks for account_identification uniqueness.

Changes

Cohort / File(s) Summary
Database Migration
services/current-account/migrations/20260416000001_drop_syndicate_scope_integrity_index.sql
Adds migration that conditionally drops idx_account_syndicate_scope_integrity using DROP INDEX IF EXISTS.
Migration Tests
services/current-account/adapters/persistence/org_scoped_migration_test.go
Test updates: remove idx_account_syndicate_scope_integrity from expected created indexes; add ScopeIntegrityIndexDropped subtest asserting the index is absent; replace uniqueness test to allow multiple accounts per (party_id, org_party_id, instrument_code) and add account_identification duplicate-insert checks.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: dropping a unique index to allow multiple accounts per (party, org, instrument) and unblocking the Demo Reset workflow.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the motivation (Demo Reset failures), the index being dropped, the rationale, and safety considerations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/payg-account-uniqueness

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 16, 2026

Claude Code Review

Commit: 4c1184c (2/2) | CI: running (migration checksums verified, security scans passed)

Summary

Well-executed fix for a 25-day nightly Demo Reset failure. The migration correctly drops idx_account_syndicate_scope_integrity -- a uniqueness constraint that assumed one account per (party, org, instrument), which does not hold for utility billing where a customer legitimately has separate GBP accounts for electricity and gas with the same supplier.

The safety argument is sound: idx_account_account_identification still enforces unique account identifiers, which is the correct uniqueness boundary. The two non-unique query indexes (idx_account_participant_syndicate, idx_account_syndicate_participants) for syndicate lookups are untouched.

Second commit (4c1184c) tightens the duplicate-key test per CodeRabbit feedback -- the assertion now verifies the error contains both "duplicate key" and "account_identification", proving the correct constraint fires.

What I verified:

  • Full migration chain: index created on currency (20260214000002), dropped+recreated on instrument_code (20260225000001/000003), now dropped (20260416000001) -- clean lifecycle
  • No application code references the dropped index -- constraint was purely at the DB level
  • Migration uses idempotent DROP INDEX IF EXISTS, CockroachDB-safe (no CONCURRENTLY, no PL/pgSQL)
  • atlas.sum properly updated, no existing migration files modified
  • Tests cover: index absence assertion, multi-service billing (same party+org+instrument succeeds), account_identification uniqueness still enforced with specific constraint name assertion, personal accounts unaffected
  • Test uses CockroachDB testcontainer for production parity

Risk Assessment

Area Level Detail
Blast radius Low Relaxes a constraint -- existing valid data unaffected, previously-rejected inserts now succeed as intended
Rollback Safe Index can be re-created in a new migration (would fail only if new violating data exists, which is the desired state)
Scale Low Dropped index was a uniqueness constraint, not a query optimization index -- the two non-unique query indexes remain
Cross-system Low No other services reference this index
Migration Safe Idempotent DROP INDEX IF EXISTS, single statement, CockroachDB-compatible

Findings

No actionable findings. The change is correct, well-tested, and appropriately scoped.

Bot Review Notes

CodeRabbit requested tightening the duplicate-key assertion (commit 1). The author addressed this in commit 2 by adding assert.Contains checks for "duplicate key" and "account_identification". The concern is fully resolved -- no unresolved bot threads remain.

Notes

  • PRD-022 (docs/prd/022-org-scoped-accounts.md) still defines idx_account_syndicate_scope_integrity in its schema specification. Worth updating in a follow-up to keep the PRD consistent with the actual schema, but not blocking for this fix.
  • The UniqueConstraintDoesNotAffectPersonalAccounts test comment references "the partial unique index only applies WHERE org_party_id IS NOT NULL" -- now slightly stale since the index no longer exists, but the test itself is still valid (personal accounts with NULL org_party_id were never affected) and outside the scope of this fix.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Clean fix — correct migration, thorough tests, sound safety analysis. See summary comment for full review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/current-account/adapters/persistence/org_scoped_migration_test.go`:
- Around line 229-247: The test currently asserts any insert failure for dup via
assert.Error(t, err) — tighten this to assert that the error is specifically a
unique-constraint violation on account_identification: after
gormDB.Create(dup).Error, use errors.As/Type assertion to detect the Postgres
error type (e.g., *pgconn.PgError or *pq.Error) and assert the SQLSTATE 23505 or
that the ConstraintName equals "idx_account_account_identification" (or assert
the error message contains "duplicate key" and the constraint name) so the test
proves the failure is due to the account_identification uniqueness constraint
rather than some other DB error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 60e5eb6b-7372-4ca0-89a1-910152b822fb

📥 Commits

Reviewing files that changed from the base of the PR and between bcf27d6 and 93f078d.

⛔ Files ignored due to path filters (1)
  • services/current-account/migrations/atlas.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • services/current-account/adapters/persistence/org_scoped_migration_test.go
  • services/current-account/migrations/20260416000001_drop_syndicate_scope_integrity_index.sql

Comment thread services/current-account/adapters/persistence/org_scoped_migration_test.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Per CodeRabbit review: assert.Error alone passes on any insert failure.
Tighten to require the failure references account_identification so the
test specifically proves uniqueness on that column survives dropping the
syndicate scope integrity index.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Clean fix -- correct migration, thorough tests, CodeRabbit feedback addressed. See summary comment for full review.

@bjcoombs bjcoombs merged commit 7b90f45 into develop Apr 16, 2026
39 checks passed
@bjcoombs bjcoombs deleted the fix/payg-account-uniqueness branch April 16, 2026 15:17
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