Skip to content

feat: separate fiat overage credit realizations from charge currency allocations - #4857

Merged
turip merged 2 commits into
mainfrom
feat/om-435-fiat-overage-credit-realizations
Aug 6, 2026
Merged

turip merged 2 commits into
mainfrom
feat/om-435-fiat-overage-credit-realizations

Conversation

@turip

@turip turip commented Aug 5, 2026

Copy link
Copy Markdown
Member

Ticket: OM-435

Summary

Custom-currency credit_then_invoice charges cross two monetary domains during realization:

  • the charge currency used for rating and charge-credit allocation
  • the settlement fiat currency used for a positive overage

This change represents those domains as separate credit-realization streams for both flat-fee and usage-based runs. Fiat-overage realizations are persisted in dedicated allocation tables, while the existing allocation tables remain the source of truth for charge-currency realizations.

Why

Fiat-overage allocations cannot safely share one realization collection with charge-currency allocations: their amounts have different units, and corrections must never cross between those monetary domains. Keeping them in separate tables gives each stream its own correction lineage. The existing self-referencing foreign keys enforce same-domain target existence, while the adapters enforce that corrections target persisted allocations from the same realization run.

The adapter boundary now makes the monetary domain explicit when writing realizations, and realization runs load charge-currency and fiat-overage allocations independently.

Domain impact

  • Existing charge-currency realizations and data remain unchanged.
  • Flat-fee and usage-based realization runs can persist and restore fiat-overage credit realizations independently.
  • Correction lineage is structurally isolated between charge currency and settlement fiat.
  • The schema change is additive and establishes the persistence model needed by OM-435.

This PR does not yet consume settlement-fiat credits for an overage; it provides the domain and adapter foundation for that behavior.

Validation

  • make migrate-check
  • make lint
  • make test — 6,778 tests passed, 9 skipped

Summary by CodeRabbit

  • New Features

    • Added support for tracking fiat overage credit realizations alongside standard credit realizations.
    • Realization runs now include fiat overage credits in returned and serialized results.
    • Added persistent storage for fiat overage credit allocations across flat-fee and usage-based billing.
  • Bug Fixes

    • Credit corrections now validate that targets belong to the same realization run and monetary domain.
    • Invalid or mixed correction requests are rejected atomically, preventing partial updates.
    • Improved handling and reporting of realization mapping errors.

Greptile Summary

The PR separates charge-currency and settlement-fiat credit realizations into independent persistence streams for flat-fee and usage-based runs.

  • Adds dedicated fiat-overage allocation entities, relationships, and migrations.
  • Routes creation, restoration, and correction through monetary-domain-specific adapters.
  • Adds same-run correction-target validation and PostgreSQL-backed adapter tests.

Confidence Score: 4/5

The PR is not yet safe to merge because allocation-only writes can persist realizations under a namespace different from their parent run.

The new correction queries enforce same-run ownership only when corrections are present, while ordinary allocation batches proceed directly to insertion using independently supplied namespace and run-ID values that the database does not constrain as a pair.

Files Needing Attention: openmeter/billing/charges/flatfee/adapter/credits.go and openmeter/billing/charges/usagebased/adapter/credits.go

Important Files Changed

Filename Overview
openmeter/billing/charges/flatfee/adapter/credits.go Splits flat-fee realization persistence by monetary domain and validates correction targets, but allocation-only writes no longer verify parent-run namespace ownership.
openmeter/billing/charges/usagebased/adapter/credits.go Mirrors the flat-fee domain-specific persistence and correction validation, including the allocation-only parent-validation gap.
openmeter/billing/charges/models/creditrealization/mixin.go Centralizes realization mapping and extraction of unique correction target IDs.
tools/migrate/migrations/20260806133101_create_charge_overage_credit_allocations.up.sql Adds separate flat-fee and usage-based fiat-overage allocation tables with same-domain self-referencing correction foreign keys.
openmeter/ent/schema/chargesflatfee.go Adds the flat-fee fiat-overage allocation entity and run relationships.
openmeter/ent/schema/chargesusagebased.go Adds the usage-based fiat-overage allocation entity and run relationships.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Run[Realization run]
  Service[Realization service]
  ChargeAdapter[Charge-currency adapter]
  FiatAdapter[Fiat-overage adapter]
  ChargeTable[(Charge-currency allocations)]
  FiatTable[(Fiat-overage allocations)]
  Service --> ChargeAdapter --> ChargeTable
  Service --> FiatAdapter --> FiatTable
  Run --> ChargeAdapter
  Run --> FiatAdapter
Loading

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
### Issue 1
openmeter/billing/charges/flatfee/adapter/credits.go:26-27
**Allocation batches bypass run scoping**

When an allocation-only batch carries an existing run ID paired with a different namespace, `CorrectionTargetIDs()` is empty, so the scoped lookup is skipped and the allocation is persisted with the supplied namespace and run ID. Because the foreign key validates only the run ID, this creates realization history whose namespace differs from its parent run, causing namespace-scoped reads to omit the allocation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (3): Last reviewed commit: "fix(billing): scope credit corrections t..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change separates charge-currency and fiat-overage credit realizations. It adds storage schemas, structured adapter inputs, same-run correction validation, realization-run mapping, service integration, and adapter test coverage for flat-fee and usage-based charges.

Changes

Credit realization persistence and service flow

Layer / File(s) Summary
Overage realization storage models
openmeter/billing/charges/models/creditrealization/*, openmeter/ent/schema/*, tools/migrate/migrations/*
Adds separate overage allocation schemas, database tables, relationships, indexes, and correction-target ID extraction.
Structured realization adapter contracts
openmeter/billing/charges/flatfee/adapter.go, openmeter/billing/charges/usagebased/adapter.go
Replaces single allocation methods with charge-currency and fiat-overage realization methods that accept validated structured inputs.
Adapter persistence and run mapping
openmeter/billing/charges/{flatfee,usagebased}/adapter/*, openmeter/billing/charges/{flatfee,usagebased}/realizationrun.go
Persists both realization types transactionally, validates same-run correction targets, preloads overage allocations, and maps both collections into realization runs.
Charge realization service wiring
openmeter/billing/charges/{flatfee,usagebased}/service/*, openmeter/billing/charges/README.md
Updates allocation and correction flows to create charge-currency credit realizations with structured inputs.
Correction validation coverage
openmeter/billing/charges/{flatfee,usagebased}/adapter/credits_test.go
Tests valid same-run corrections, invalid targets, and atomic rejection of mixed valid and invalid batches.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RealizationService
  participant CreditAllocationAdapter
  participant EntDatabase
  participant RealizationRun
  RealizationService->>CreditAllocationAdapter: Submit charge-currency or fiat-overage input
  CreditAllocationAdapter->>EntDatabase: Check same-run correction targets
  CreditAllocationAdapter->>EntDatabase: Save realization allocations in a transaction
  EntDatabase-->>CreditAllocationAdapter: Return persisted allocations
  CreditAllocationAdapter-->>RealizationService: Return credit realizations
  RealizationRun->>EntDatabase: Load charge-currency and fiat-overage allocations
  EntDatabase-->>RealizationRun: Return mapped realization collections
Loading

Possibly related PRs

Suggested reviewers: tothandras, galexihu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: separating fiat overage credit realizations from charge-currency allocations.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/om-435-fiat-overage-credit-realizations

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.

@turip turip changed the title OM-435 Separate fiat overage credit realizations from charge currency allocations Separate fiat overage credit realizations from charge currency allocations Aug 5, 2026
@turip turip added area/billing release-note/feature Release note: Exciting New Features labels Aug 5, 2026
@turip
turip marked this pull request as ready for review August 5, 2026 14:33
@turip
turip requested a review from a team as a code owner August 5, 2026 14:33
@turip turip changed the title Separate fiat overage credit realizations from charge currency allocations feat: separate fiat overage credit realizations from charge currency allocations Aug 5, 2026
@turip
turip force-pushed the feat/om-435-fiat-overage-credit-realizations branch from 1c8b7dd to 79cdb5c Compare August 6, 2026 13:38
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Comment on lines +26 to +27
correctionTargetIDs := input.CreditRealizations.CorrectionTargetIDs()
if len(correctionTargetIDs) > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Allocation batches bypass run scoping

When an allocation-only batch carries an existing run ID paired with a different namespace, CorrectionTargetIDs() is empty, so the scoped lookup is skipped and the allocation is persisted with the supplied namespace and run ID. Because the foreign key validates only the run ID, this creates realization history whose namespace differs from its parent run, causing namespace-scoped reads to omit the allocation.

Knowledge Base Used: Data layer: ent schema, migrations, and generated client

Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/billing/charges/flatfee/adapter/credits.go
Line: 26-27

Comment:
**Allocation batches bypass run scoping**

When an allocation-only batch carries an existing run ID paired with a different namespace, `CorrectionTargetIDs()` is empty, so the scoped lookup is skipped and the allocation is persisted with the supplied namespace and run ID. Because the foreign key validates only the run ID, this creates realization history whose namespace differs from its parent run, causing namespace-scoped reads to omit the allocation.

**Knowledge Base Used:** [Data layer: ent schema, migrations, and generated client](https://app.greptile.com/openmeter/-/custom-context/knowledge-base/openmeterio/openmeter/-/docs/data-layer.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@turip
turip enabled auto-merge (squash) August 6, 2026 13:41
@turip
turip merged commit ef9f6e9 into main Aug 6, 2026
26 checks passed
@turip
turip deleted the feat/om-435-fiat-overage-credit-realizations branch August 6, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants