Skip to content

feat(ledger): add spendable balances for FBO collection - #5270

Merged
GAlexIHU merged 4 commits into
mainfrom
codex/fbo-collection-plans
Oct 26, 2026
Merged

GAlexIHU merged 4 commits into
mainfrom
codex/fbo-collection-plans

Conversation

@GAlexIHU

@GAlexIHU GAlexIHU commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Collecting against a historical balance could reuse credit already allocated to later usage. Collection now uses each source's minimum balance from the selection time through recorded future postings, preserving those later allocations.

Move source selection, consumption transactions, breakage releases, and shortfall advances into ledger/fbo. Consume handles one collection request and returns resolved transaction inputs. The collector owns the database transaction, commits the group, and creates billing allocations.

The ledger gains a generic minimum-balance query. FBO calculates minima per source before combining buckets and excludes future breakage because consumption releases it; breakage at or before the boundary remains effective. Ordinary account balances retain their historical meaning.

Validation on the rebased stack: PostgreSQL-backed minimum-balance, FBO, collector, and correction tests plus scoped Go lint passed. Coverage includes future spending and grants, simultaneous postings, independent source minima, and breakage boundaries.

Stack: #5252 → #5270 → #5294 → #5304. Depends on #5252; booking-time alignment follows in #5294.

RetriggerView in GreptileConfidence Score: 4/5

The PR should not merge until repeated Consume calls cannot select the same unposted credit twice.

Fix All in Claude CodeFindings

  1. P1 Customer credit spent twice ▶
  2. P2 Source reads hide transaction requirements ▶
Fix with agent prompt
### Issue 1
openmeter/ledger/fbo/consume.go:217-222
`Consume` no longer reserves selected credit. If a caller makes two calls in one database transaction before posting their shared group, both read the same FBO balance. With 10 credits and two requests for 7, both return transactions consuming 7 instead of leaving only 3 for the second request.

`CommitGroup` does not check available balances, so posting these inputs leaves FBO at -4 and skips any required shortfall advance. Keep reservations across calls, or reject repeated consumption before posting.

### Issue 2
openmeter/ledger/fbo/source_query.go:57-58
`ListSources` needs a caller-owned PostgreSQL transaction, but its public comment does not say so. It calls `LockForPosting`, which fails without a transaction and holds customer account locks until that transaction ends. Document this requirement and lock lifetime so callers do not treat it as an ordinary balance read.

```suggestion
// ListSources returns ordered eligible source slices at the historical boundary.
// It requires a caller-owned PostgreSQL transaction and holds the customer's FBO,
// receivable, and accrued account locks until that transaction ends.
// It uses the current collection policy; it is not the future spendable-balance API.
```

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

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

Summary

Adds minimum-balance queries so collection preserves credit already assigned to later spending.

  • FBO collections preserve credit already assigned to later usage.
  • The FBO service plans collections, and the collector commits them.

Diagram

sequenceDiagram
  participant Collector
  participant FBO
  participant Ledger
  participant Breakage
  Collector->>FBO: Consume within caller transaction
  FBO->>Ledger: Read minimum balances per source
  FBO->>Breakage: Record releases for selected credit
  FBO-->>Collector: Return consumption, release, and advance inputs
  Collector->>Ledger: Commit group
  Collector-->>Collector: Create billing allocations
Loading

Reviews (8) · Last reviewed commit: "feat(ledger): preserve future spending i..." · Reviewed by Greptile

@GAlexIHU GAlexIHU added the release-note/misc Miscellaneous changes label Oct 8, 2026
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 14d457f and 9188073.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review.

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: openmeterio/openmeter/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 990c8da8-7ce4-428f-a8c8-29cdf1cb29ac
📥 Commits

Reviewing files that changed from the base of the PR and between 609d2f4 and 0d8ece0.

📒 Files selected for processing: 1
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes: 1
  • AGENTS.md

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

Walkthrough

The ledger adds minimum-balance queries. The FBO service uses them to report spendable balances and list sources, and handles credit consumption, breakage releases, and advances. The collector delegates consumption to the FBO service and commits the returned transaction group.

Changes: Ledger balances and FBO consumption

Layer / File(s) Summary
Minimum-balance query and historical calculation
openmeter/ledger/balance.go, openmeter/ledger/historical/..., openmeter/ledger/noop/noop.go, openmeter/ledger/README.md
The ledger adds a minimum-balance query that requires AsOf and rejects filters that truncate posting history. The historical adapter calculates bucket minima across the boundary and later movements. Tests cover effective times, simultaneous postings, opening balances, and annotation exclusions.
FBO source listing and spendable balances
openmeter/ledger/fbo/service.go, openmeter/ledger/fbo/balance.go, openmeter/ledger/fbo/source_query.go, openmeter/ledger/fbo/sources*.go, openmeter/ledger/fbo/balance_test.go
The FBO service validates source and balance queries, lists eligible sources, and reports positive spendable balances using minimum-balance buckets. Tests cover source-specific minima, future spending, and breakage boundaries.
FBO consumption and breakage release
openmeter/ledger/fbo/consume.go, openmeter/ledger/fbo/consume_test.go, openmeter/ledger/fbo/README.md
The service validates consumption requests, selects credits, returns transaction inputs, releases matching breakage, and supports advances under the configured policy. Tests cover grouped transactions and rollback.
Collector integration and transaction commits
openmeter/ledger/collector/collect.go, openmeter/ledger/collector/service.go, openmeter/ledger/collector/README.md, openmeter/ledger/collector/*_test.go, openmeter/ledger/README.md
The collector delegates accrued and receivable consumption to the FBO service and commits returned inputs under a generated group ID. Tests and documentation reflect FBO-owned source selection and breakage handling.

Comment-writing guidance

Layer / File(s) Summary
Comment guidance and example
AGENTS.md
The guidance asks contributors to use plain, concrete language in comments, retain useful explanations, and compare verbose and concise Go comment examples.

Priority: Normal

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Collector
  participant FBOService
  participant BreakageService
  participant Ledger
  Collector->>FBOService: Submit Consume request
  FBOService->>FBOService: Select credits and resolve transaction inputs
  FBOService->>BreakageService: Record releases for consumed sources
  FBOService-->>Collector: Return transaction inputs
  Collector->>Ledger: Commit inputs as a transaction group
Loading
Merge Risk: Minimal · up to 0d8ec

This update only adds documentation guidance for writing comments and does not change runtime behavior, so it carries no actionable merge risk.

Pre-merge checks: Passed 4
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.
Title check Passed The title clearly identifies the addition of spendable balances for FBO collection, which is a substantial part of the changeset.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Comment thread openmeter/ledger/fbo/service.go Outdated
Comment thread openmeter/ledger/fbo/source_query.go Outdated
Comment on lines +57 to +58
// ListSources returns ordered eligible source slices at the historical boundary.
// It uses the current collection policy; it is not the future spendable-balance API.

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.

P2 Source reads hide transaction requirements

ListSources needs a caller-owned PostgreSQL transaction, but its public comment does not say so. It calls LockForPosting, which fails without a transaction and holds customer account locks until that transaction ends. Document this requirement and lock lifetime so callers do not treat it as an ordinary balance read.

Suggested change
// ListSources returns ordered eligible source slices at the historical boundary.
// It uses the current collection policy; it is not the future spendable-balance API.
// ListSources returns ordered eligible source slices at the historical boundary.
// It requires a caller-owned PostgreSQL transaction and holds the customer's FBO,
// receivable, and accrued account locks until that transaction ends.
// It uses the current collection policy; it is not the future spendable-balance API.
Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/ledger/fbo/source_query.go
Line: 57-58

Comment:
**Source reads hide transaction requirements**

`ListSources` needs a caller-owned PostgreSQL transaction, but its public comment does not say so. It calls `LockForPosting`, which fails without a transaction and holds customer account locks until that transaction ends. Document this requirement and lock lifetime so callers do not treat it as an ordinary balance read.

```suggestion
// ListSources returns ordered eligible source slices at the historical boundary.
// It requires a caller-owned PostgreSQL transaction and holds the customer's FBO,
// receivable, and accrued account locks until that transaction ends.
// It uses the current collection policy; it is not the future spendable-balance API.
```

---

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

Comment on lines +217 to +222
sources, err := s.listCustomerFBOSources(ctx, SourceQuery{
CustomerID: request.CustomerID,
Currency: request.Currency,
Filters: request.Filters,
AsOf: request.SourceBalanceAsOf,
})

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 Customer credit spent twice

Consume no longer reserves selected credit. If a caller makes two calls in one database transaction before posting their shared group, both read the same FBO balance. With 10 credits and two requests for 7, both return transactions consuming 7 instead of leaving only 3 for the second request.

CommitGroup does not check available balances, so posting these inputs leaves FBO at -4 and skips any required shortfall advance. Keep reservations across calls, or reject repeated consumption before posting.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/ledger/fbo/consume.go
Line: 217-222

Comment:
**Customer credit spent twice**

`Consume` no longer reserves selected credit. If a caller makes two calls in one database transaction before posting their shared group, both read the same FBO balance. With 10 credits and two requests for 7, both return transactions consuming 7 instead of leaving only 3 for the second request.

`CommitGroup` does not check available balances, so posting these inputs leaves FBO at -4 and skips any required shortfall advance. Keep reservations across calls, or reject repeated consumption before posting.

---

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

@GAlexIHU
GAlexIHU marked this pull request as ready for review October 14, 2026 10:47
@GAlexIHU
GAlexIHU requested a review from a team as a code owner October 14, 2026 10:47
@GAlexIHU
GAlexIHU added this pull request to stack #5295 October 14, 2026 10:47
@GAlexIHU
GAlexIHU force-pushed the codex/fbo-collection-plans branch from 609d2f4 to 0d8ece0 Compare October 19, 2026 13:16
@GAlexIHU GAlexIHU changed the title refactor(ledger): plan FBO consumption in a shared group feat(ledger): add spendable balances for FBO collection Oct 19, 2026
@GAlexIHU GAlexIHU added the release-note/feature Release note: Exciting New Features label Oct 19, 2026
@GAlexIHU GAlexIHU removed the release-note/misc Miscellaneous changes label Oct 19, 2026
Base automatically changed from codex/ledger-preassigned-ids-breakage to main October 19, 2026 13:36
@GAlexIHU
GAlexIHU force-pushed the codex/fbo-collection-plans branch from 0d8ece0 to c680681 Compare October 19, 2026 13:36
@GAlexIHU
GAlexIHU force-pushed the codex/fbo-collection-plans branch from c680681 to 14d457f Compare October 21, 2026 13:44
@GAlexIHU
GAlexIHU force-pushed the codex/fbo-collection-plans branch from 14d457f to 9188073 Compare October 26, 2026 11:02
@GAlexIHU
GAlexIHU merged commit a6670ea into main Oct 26, 2026
32 checks passed
@GAlexIHU
GAlexIHU deleted the codex/fbo-collection-plans branch October 26, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants