Skip to content

Conversation

@GAlexIHU
Copy link
Contributor

@GAlexIHU GAlexIHU commented Oct 29, 2025

Overview

Exposes new field

Summary by CodeRabbit

Release Notes

  • New Features
    • Entitlement values now include total available grant amount, displaying the cumulative sum of all active grants available at query time for improved financial visibility and planning.

@GAlexIHU GAlexIHU requested a review from a team as a code owner October 29, 2025 16:36
@GAlexIHU GAlexIHU added the kind/feature New feature or request label Oct 29, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

📝 Walkthrough

Walkthrough

The PR threads a new TotalAvailableGrantAmount field through the entitlement value chain, from API spec down to engine calculations and API responses. It also refactors the credit balance interface by making GetBalanceSinceSnapshot private and switches calls to GetBalanceAt, while capturing run parameters in results for downstream use.

Changes

Cohort / File(s) Summary
API Specification & Models
api/spec/src/entitlements/subjects.tsp, openmeter/entitlement/snapshot/event.go
Adds public TotalAvailableGrantAmount field to EntitlementValue models with JSON serialization tags
Credit Engine
openmeter/credit/engine/engine.go, openmeter/credit/engine/run.go, openmeter/credit/balance.go
Introduces TotalAvailableGrantAmount() and Clone() methods on RunParams; adds RunParams field to RunResult; makes GetBalanceSinceSnapshot unexported on BalanceConnector interface
Entitlement Metered Calculations
openmeter/entitlement/metered/balance.go, openmeter/entitlement/metered/connector.go
Adds TotalAvailableGrantAmount field; switches from GetBalanceSinceSnapshot to GetBalanceAt; populates field from engine result's RunParams
API Response Mapping
openmeter/entitlement/driver/parser.go
Maps TotalAvailableGrantAmount to API response in MapEntitlementValueToAPI
Tests
e2e/e2e_test.go
Updates EntitlementValue struct to include TotalAvailableGrantAmount *float64 in test expectations

Sequence Diagram

sequenceDiagram
    participant Engine as Credit Engine
    participant Balance as Entitlement Balance
    participant Connector as Metered Connector
    participant API as API Response

    Engine->>Engine: RunParams.Clone()
    Engine->>Engine: Calculate TotalAvailableGrantAmount()
    Engine->>Engine: Store RunParams in RunResult
    
    Balance->>Engine: res.RunParams.TotalAvailableGrantAmount()
    Balance->>Balance: Populate EntitlementBalance.TotalAvailableGrantAmount
    
    Connector->>Balance: Get balance with TotalAvailableGrantAmount
    Connector->>Connector: Create MeteredEntitlementValue
    
    Connector->>API: Map to EntitlementValue
    API->>API: Expose TotalAvailableGrantAmount in response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Interface change: GetBalanceSinceSnapshot refactored from public to private—verify no external callers exist and all internal call sites are updated
  • Logic refactor in metered/balance.go: Removal of GetLastValidSnapshotAt and switch to GetBalanceAt—confirm this doesn't change behavior or lose context
  • Clone and calculation methods: Verify RunParams.Clone() correctly deep-copies all fields, and TotalAvailableGrantAmount() preserves decimal precision via alpacadecimal

Possibly related PRs

Suggested labels

area/api, release-note/feature, release-note/breaking-change

Suggested reviewers

  • tothandras
  • hekike

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title references "TotalAvailableGrantedAmount," but the actual implementation throughout the changeset uses "TotalAvailableGrantAmount" (without the "ed"). This naming mismatch appears consistently across all modified files—the API spec, engine methods, balance calculations, and event snapshots all refer to "TotalAvailableGrantAmount." While the core intent of the title (exposing a new grant-related field) aligns with the changes, the specific field name in the title is inaccurate and doesn't match what's actually being exposed, which could create confusion during code review. Update the PR title to correctly reflect the actual field name: "feat(entitlement): expose TotalAvailableGrantAmount" (removing the "ed" from "Granted"). This will ensure the title accurately describes what's being implemented and matches the naming throughout the codebase.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/entitlements/tag

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 and usage tips.

Copy link
Contributor

@chrisgacsal chrisgacsal left a comment

Choose a reason for hiding this comment

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

Awesome! :shipit:

Copy link
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: 2

🧹 Nitpick comments (2)
api/spec/src/entitlements/subjects.tsp (1)

233-238: Consider clarifying "active" grants in the documentation.

The documentation mentions "grant amounts that are active at the time of the query" but doesn't specify what makes a grant active. Consider adding a brief note about whether this includes grants that haven't reached their effective date yet, or have expired, etc. This will help API consumers understand the field better.

e2e/e2e_test.go (1)

1104-1108: Test validates the new field, but could be more thorough.

The test correctly expects TotalAvailableGrantAmount to be 100.0, matching the grant created earlier. However, consider adding test cases for:

  • Multiple grants (to verify summation)
  • Zero grants (to verify the field handles empty cases)
  • Expired or inactive grants (to verify only "active" grants are counted)

This would give more confidence that the calculation logic is robust.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58c348d and 4d69745.

⛔ Files ignored due to path filters (5)
  • api/client/go/client.gen.go is excluded by !api/client/**
  • api/client/javascript/src/client/schemas.ts is excluded by !api/client/**
  • api/client/python/openmeter/_generated/models/_models.py is excluded by !**/_generated/**, !api/client/**
  • api/openapi.cloud.yaml is excluded by !**/openapi.cloud.yaml
  • api/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (9)
  • api/spec/src/entitlements/subjects.tsp (1 hunks)
  • e2e/e2e_test.go (1 hunks)
  • openmeter/credit/balance.go (2 hunks)
  • openmeter/credit/engine/engine.go (2 hunks)
  • openmeter/credit/engine/run.go (2 hunks)
  • openmeter/entitlement/driver/parser.go (1 hunks)
  • openmeter/entitlement/metered/balance.go (2 hunks)
  • openmeter/entitlement/metered/connector.go (2 hunks)
  • openmeter/entitlement/snapshot/event.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

⚙️ CodeRabbit configuration file

**/*.go: In general when reviewing the Golang code make readability and maintainability a priority, even potentially suggest restructuring the code to improve them.

Performance should be a priority in critical code paths. Anything related to event ingestion, message processing, database operations (regardless of database) should be vetted for potential performance bottlenecks.

Files:

  • openmeter/credit/engine/run.go
  • openmeter/entitlement/snapshot/event.go
  • openmeter/entitlement/metered/balance.go
  • openmeter/entitlement/metered/connector.go
  • openmeter/entitlement/driver/parser.go
  • e2e/e2e_test.go
  • openmeter/credit/engine/engine.go
  • openmeter/credit/balance.go
**/*_test.go

⚙️ CodeRabbit configuration file

**/*_test.go: Make sure the tests are comprehensive and cover the changes. Keep a strong focus on unit tests and in-code integration tests.
When appropriate, recommend e2e tests for critical changes.

Files:

  • e2e/e2e_test.go
**/*.tsp

⚙️ CodeRabbit configuration file

**/*.tsp: Review the TypeSpec code for conformity with TypeSpec best practices. When recommending changes also consider the fact that multiple codegeneration toolchains depend on the TypeSpec code, each of which have their idiosyncrasies and bugs.

The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user.

Files:

  • api/spec/src/entitlements/subjects.tsp
🧬 Code graph analysis (6)
openmeter/credit/engine/run.go (2)
openmeter/credit/balance/balance.go (1)
  • Snapshot (80-85)
openmeter/credit/engine/engine.go (1)
  • RunParams (16-30)
openmeter/entitlement/metered/balance.go (2)
openmeter/credit/balance/balance.go (1)
  • Snapshot (80-85)
openmeter/credit/engine/engine.go (1)
  • RunParams (16-30)
openmeter/entitlement/metered/connector.go (2)
openmeter/ent/db/balancesnapshot/where.go (2)
  • Balance (84-86)
  • Overage (89-91)
openmeter/ent/db/entitlement/where.go (1)
  • IsSoftLimit (140-142)
e2e/e2e_test.go (1)
pkg/convert/ptr.go (1)
  • ToPointer (10-12)
openmeter/credit/engine/engine.go (6)
openmeter/credit/grant/grant.go (1)
  • Grant (14-60)
pkg/timeutil/timeline.go (1)
  • NewSimpleTimeline (11-18)
openmeter/meter/meter.go (1)
  • Meter (139-148)
openmeter/credit/grant/owner_connector.go (1)
  • ResetBehavior (32-34)
openmeter/credit/balance/balance.go (1)
  • Snapshot (80-85)
openmeter/credit/engine/history.go (1)
  • GrantBurnDownHistory (93-96)
openmeter/credit/balance.go (3)
pkg/models/id.go (1)
  • NamespacedID (7-10)
openmeter/credit/balance/balance.go (1)
  • Snapshot (80-85)
openmeter/credit/engine/engine.go (1)
  • RunResult (58-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Artifacts / Container image
  • GitHub Check: Lint
  • GitHub Check: Test
  • GitHub Check: Migration Checks
  • GitHub Check: Code Generators
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
openmeter/credit/balance.go (1)

43-44: Nice encapsulation improvement!

Making getBalanceSinceSnapshot private is a solid move since it was never exposed through the BalanceConnector interface. This keeps implementation details internal while the public API remains clean.

openmeter/entitlement/snapshot/event.go (1)

55-56: LGTM! Field addition is clean and consistent.

The new TotalAvailableGrantAmount field aligns perfectly with the API spec, using the correct Go patterns (pointer for optional field, omitempty JSON tag).

openmeter/credit/engine/run.go (1)

17-17: Nice defensive programming with the param cloning!

Cloning the input params and returning them in the result ensures the RunResult carries an immutable snapshot of the parameters used for the calculation. This makes the result self-contained and easier to reason about.

Also applies to: 95-97

openmeter/entitlement/driver/parser.go (1)

160-164: Clean mapping implementation!

The field mapping correctly threads TotalAvailableGrantAmount from the internal MeteredEntitlementValue to the API response. Only populating it for metered entitlements makes sense since grants are specific to that entitlement type.

openmeter/entitlement/metered/connector.go (1)

49-54: Straightforward field addition, well integrated!

The TotalAvailableGrantAmount field is cleanly added to MeteredEntitlementValue and correctly populated from the balance. The struct fields are nicely organized alongside other balance-related properties.

Also applies to: 132-137

openmeter/credit/engine/engine.go (2)

32-38: Clean implementation of grant amount summation!

Using alpacadecimal for the intermediate calculation helps maintain precision before converting back to float64. This aligns with the established pattern in the codebase and the acknowledgment on line 75 that float64 limitations are a known concern.


63-64: RunParams field addition makes results more informative!

Embedding the exact parameters used to produce the result makes RunResult self-contained and easier to debug. Nice enhancement to the API.

openmeter/entitlement/metered/balance.go (3)

22-29: Nice addition!

The new TotalAvailableGrantAmount field is properly defined with the correct JSON tag. The structure looks clean and consistent with the existing fields.


83-90: Looking good!

The new field is properly populated from res.RunParams.TotalAvailableGrantAmount(), and all the existing fields are correctly mapped. The balance calculation flow makes sense.


78-78: Method change verified—behavior is equivalent.

The refactoring from GetBalanceSinceSnapshot to GetBalanceAt is solid. The BalanceConnector interface only exposes GetBalanceAt (line 31 in openmeter/credit/balance.go), and it's explicitly designed to "minimize execution cost by calculating from the latest valid snapshot." That's exactly what the old internal getBalanceSinceSnapshot helper was doing, so the semantics are identical. The error message "failed to get balance since snapshot" remains accurate since GetBalanceAt handles that optimization internally.

Comment on lines +40 to +56
func (p RunParams) Clone() RunParams {
grants := make([]grant.Grant, len(p.Grants))
copy(grants, p.Grants)

resets := timeutil.NewSimpleTimeline(p.Resets.GetTimes())

res := RunParams{
Meter: p.Meter,
Grants: grants,
Until: p.Until,
StartingSnapshot: p.StartingSnapshot,
ResetBehavior: p.ResetBehavior,
Resets: resets,
}

return res
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clone method has a shallow copy issue with Meter.GroupBy.

The Clone() method copies the Meter field by value, but Meter.GroupBy is a map[string]string (reference type). If the original Meter.GroupBy is mutated after cloning, it will affect the cloned RunParams too. Consider deep copying the map:

 func (p RunParams) Clone() RunParams {
 	grants := make([]grant.Grant, len(p.Grants))
 	copy(grants, p.Grants)
 
 	resets := timeutil.NewSimpleTimeline(p.Resets.GetTimes())
+
+	// Deep copy Meter.GroupBy map
+	meterCopy := p.Meter
+	if p.Meter.GroupBy != nil {
+		meterCopy.GroupBy = make(map[string]string, len(p.Meter.GroupBy))
+		for k, v := range p.Meter.GroupBy {
+			meterCopy.GroupBy[k] = v
+		}
+	}
 
 	res := RunParams{
-		Meter:            p.Meter,
+		Meter:            meterCopy,
 		Grants:           grants,
 		Until:            p.Until,
 		StartingSnapshot: p.StartingSnapshot,
 		ResetBehavior:    p.ResetBehavior,
 		Resets:           resets,
 	}
 
 	return res
 }
🤖 Prompt for AI Agents
In openmeter/credit/engine/engine.go around lines 40 to 56, the Clone method
performs a shallow copy of p.Meter so its GroupBy map (map[string]string) is
shared between original and clone; modify Clone to deep-copy the Meter by
creating a new Meter value, allocate a new map for its GroupBy, and copy all
key/value pairs from p.Meter.GroupBy into that new map before assigning the new
Meter to res.Meter so mutations to the original Meter.GroupBy do not affect the
clone.

res, err := e.balanceConnector.GetBalanceSinceSnapshot(ctx, nsOwner, lastSnap, at)
res, err := e.balanceConnector.GetBalanceAt(ctx, nsOwner, at)
if err != nil {
return nil, fmt.Errorf("failed to get balance since snapshot: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the error message to match the method call.

The error message still references "balance since snapshot" but the code now calls GetBalanceAt on line 78. Let's keep them in sync for easier debugging.

Apply this diff:

-		return nil, fmt.Errorf("failed to get balance since snapshot: %w", err)
+		return nil, fmt.Errorf("failed to get balance at time: %w", err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return nil, fmt.Errorf("failed to get balance since snapshot: %w", err)
return nil, fmt.Errorf("failed to get balance at time: %w", err)
🤖 Prompt for AI Agents
In openmeter/entitlement/metered/balance.go around line 80, the error message
still says "failed to get balance since snapshot" while the code calls
GetBalanceAt on line 78; update the fmt.Errorf message to reference GetBalanceAt
(for example "failed to get balance at snapshot" or "failed to get balance at
specified time") so the log matches the method being called and aids debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants