Skip to content

feat(store): billing reads skip soft-deleted rows - #1962

Open
rohilsurana wants to merge 4 commits into
mainfrom
soft-delete-billing-reads
Open

rohilsurana wants to merge 4 commits into
mainfrom
soft-delete-billing-reads

Conversation

@rohilsurana

@rohilsurana rohilsurana commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

The billing customer, transaction and invoice repositories read rows without ever checking deleted_at. They now skip deleted rows, using the same live and fromLive helpers as the rest of the store.

Nothing writes deleted_at to these three tables yet, so every read returns the same rows as before. This is groundwork so that soft-deleting billing rows on org delete is safe to turn on later.

Changes

  • Eleven reads now filter on deleted_at: the reads by id, the lists, and the three balance sums.
  • Filtering the balance sums keeps the spending check in CreateEntry and the balance shown to the user in agreement, since both read through the same helpers.
  • The admin invoice search joins billing customers and organizations, so all three tables are filtered there.
  • A comment in the org delete cascade records an ordering problem the coming soft deletes will cause.

Technical Details

The plain invoice List reads one table and is left that way, so an invoice under a deleted customer still shows up there. That matches every other single-table list in the store, and the admin search is where the customer and the org get checked. A test pins both, so the difference is deliberate rather than something I missed.

The update paths are untouched. So are the hard deletes; moving those to soft deletes is separate work.

That separate work has an ordering problem worth flagging now. Once the cascade soft-deletes, an invoice can only be marked deleted after its customer is, and the loop deletes the customer last. The comment sits on the line that will break.

No migration. All three tables already have the column.

Test Plan

  • go test -run 'TestBillingCustomerRepository|TestBillingTransactionRepository|TestBillingInvoiceRepository|TestSearchInvalidUUID' ./internal/store/postgres/ passes
  • Each new suite fails on main
  • golangci-lint run ./internal/store/postgres/... ./core/deleter/... reports no issues

Each repository gets a postgres-backed test that seeds a live row and a deleted one, and I watched each fail before adding the filter. The balances came back 135, 135, 100 and 50 against the expected 95, 95, 70 and 30, which is every deleted row still being counted. The admin search returned four invoices before the change and one after. Reverting each repository file on its own was also checked, and every new test fails when its own change is taken away.

The eight pinned SQL strings in the invoice unit test were regenerated. The params are unchanged, since IS NULL binds nothing.

SQL Safety (if your PR touches *_repository.go or goqu.*)

  • Values flow through ? placeholders, goqu.Ex{}, or goqu.Record{} — never fmt.Sprintf or + building a query that gets executed.
  • ToSQL() callers capture and forward params (query, params, err := stmt.ToSQL(); db.…Context(ctx, …, query, params...)). Never query, _, err := ….
  • No ? placeholders inside single-quoted SQL literals in goqu.L (use make_interval(hours => ?)-style functions instead).
  • Any //nolint:forbidigo or // #nosec G20x annotation has a one-line justification on the same line that a reviewer can verify.

The added predicates come from the existing live() helper and bind no values. The only fmt.Sprintf in the diff builds TRUNCATE in the new test teardowns from package table constants, the same as the other suites in this package. No new lint or gosec annotations.

@vercel

vercel Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontier Ready Ready Preview Sep 30, 2026 12:48pm UTC

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: raystack/frontier/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: a952fce5-ca16-41c1-a8ca-d1ffe7cdda52

📥 Commits

Reviewing files that changed from the base of the PR and between 5b74374 and 40b67c6.

📒 Files selected for processing (8)
  • core/deleter/service.go
  • internal/store/postgres/billing_customer_repository.go
  • internal/store/postgres/billing_customer_repository_pg_test.go
  • internal/store/postgres/billing_invoice_repository.go
  • internal/store/postgres/billing_invoice_repository_pg_test.go
  • internal/store/postgres/billing_invoice_repository_test.go
  • internal/store/postgres/billing_transactions_repository.go
  • internal/store/postgres/billing_transactions_repository_pg_test.go

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Billing customer and invoice lookups and lists now exclude soft-deleted records.
    • Invoice search excludes results associated with soft-deleted invoices, customers, or organizations.
    • Billing transaction lists and balance calculations now ignore soft-deleted transactions. Spending attempts involving a deleted customer are rejected.

Walkthrough

Billing customer, invoice, and transaction queries now exclude soft-deleted records in specified lookups, lists, searches, and balance calculations. PostgreSQL tests cover these filters and their effects on returned records and spending checks. A TODO comment documents an invoice and customer deletion-order constraint.

Changes

Billing repository reads

Layer / File(s) Summary
Billing customer reads
internal/store/postgres/billing_customer_repository.go, internal/store/postgres/billing_customer_repository_pg_test.go
Customer lookups, details, and lists use live records. PostgreSQL tests check that deleted customers are excluded.
Invoice reads and search
core/deleter/service.go, internal/store/postgres/billing_invoice_repository.go, internal/store/postgres/billing_invoice_repository_test.go, internal/store/postgres/billing_invoice_repository_pg_test.go
Invoice lookups and lists filter deleted invoices. Search filters deleted invoices, customers, and organizations. Tests check query predicates and PostgreSQL results. A TODO comment describes an invoice and customer deletion-order constraint.
Transaction reads and balances
internal/store/postgres/billing_transactions_repository.go, internal/store/postgres/billing_transactions_repository_pg_test.go
Transaction lookups, lists, and balance queries use live records. PostgreSQL tests cover deleted transactions, balances, metadata filtering, and spending checks.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Suggested reviewers: amangit07

Merge Risk: ⚪ Minimal · up to 40b67

Billing reads and balances exclude soft-deleted records as intended, with tests covering filtering and spending checks. No actionable merge-blocking issue is identified; merge after normal checks pass.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 40b67

The changes narrow billing visibility without expanding tenant access or administrative privileges. Balance reads and spending checks use the same filtering rule. No newly exploitable security issue was identified, but operational database writers and concurrent deletion behavior remain incompletely verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed exposure is limited to billing record visibility and balances derived from deleted transactions. Tenant invoice listing remains customer-scoped; cross-organization invoice search and listing remain mapped to the existing superuser checks. The new predicates reduce results rather than expand that scope.

Trust Boundaries and Controls

  • observed — The tenant invoice route maps the request organization ID to an organization update-permission check. Its handler derives the billing customer from that organization and passes the customer ID to the invoice service. These mappings and caller scoping are unchanged across the canonical base and head.

Resilience and Maintainability Implications

  • observed — Customer identity and credit-limit lookups occur before CreateEntry opens its transaction. The new customer predicates reject already-deleted customers at preflight, but do not establish protection against deletion after that check. This transaction boundary predates the PR; no introduced or worsened race was established.

Hardening Proposals

  • proposed — Before enabling billing soft-delete writers, define and validate the lifecycle contract for customer liveness, invoice ordering and paired ledger visibility. Include concurrent spending, interrupted deletion, retry and restoration so deleting a debit cannot unintentionally restore spendable credit.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 36717147649

Coverage increased (+0.8%) to 53.743%

Details

  • Coverage increased (+0.8%) from the base build.
  • Patch coverage: 12 of 12 lines across 3 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 41349
Covered Lines: 22222
Line Coverage: 53.74%
Coverage Strength: 17.17 hits per line

💛 - Coveralls

This branch was successfully deployed

1 active deployment
Preview — 40b67c68 Deployed Sep 30, 2026 by vercel[bot]
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.

2 participants