Skip to content

fix: ignore invalid entitlement value#4270

Draft
chrisgacsal wants to merge 1 commit into
mainfrom
fix/invalid-entitlement-value
Draft

fix: ignore invalid entitlement value#4270
chrisgacsal wants to merge 1 commit into
mainfrom
fix/invalid-entitlement-value

Conversation

@chrisgacsal
Copy link
Copy Markdown
Collaborator

@chrisgacsal chrisgacsal commented May 2, 2026

Overview

Validate entitlement value before processing balance threshold events.

Summary by CodeRabbit

  • New Features

    • Introduced explicit entitlement value validation and a new exported validation error.
  • Bug Fixes

    • Stricter entitlement validation: negative or inconsistent entitlement values are now detected and reported.
    • Per-threshold availability and activation logic refined: usage now includes overage, percentage thresholds validate against grants, and balance thresholds preserve prior activation behavior with correctness fixes.
  • Tests

    • Expanded tests for invalid-entitlement scenarios, boundary cases, and explicit error assertions.

@chrisgacsal chrisgacsal self-assigned this May 2, 2026
@chrisgacsal chrisgacsal requested a review from a team as a code owner May 2, 2026 07:51
@chrisgacsal chrisgacsal added the release-note/bug-fix Release note: Bug Fixes label May 2, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a metered-entitlement validation API and exported ErrInvalidEntitlementValue, validates entitlement values in the threshold selection flow, changes per-threshold guards (use totalGrants vs absolute zero), adjusts activation logic to use usage+overage for certain types, and updates tests to assert new validation/error cases.

Changes

Entitlement validation → Threshold evaluation

Layer / File(s) Summary
Validation API & Errors
openmeter/entitlement/snapshot/event.go
Adds ErrInvalidEntitlementValue, ValidateMeteredEntitlementValue, and EntitlementValue.ValidateWith(...) plus a compile-time CustomValidator assertion.
Validation Unit Tests
openmeter/entitlement/snapshot/event_test.go
New table-driven tests exercising invalid/valid metered entitlement values (negative fields, mismatched grants, no-grants valid case).
Consumer wiring: validation call
openmeter/notification/consumer/entitlementbalancethreshold.go
Calls value.ValidateWith(snapshot.ValidateMeteredEntitlementValue) early in getActiveThresholdsWithHighestPriority; propagates validation errors.
Threshold selection logic
openmeter/notification/consumer/entitlementbalancethreshold.go
getNumericThreshold now accepts snapshot.EntitlementValue, uses absoluteZero and per-threshold totalGrants <= absoluteZero guards, returns ErrNoBalanceAvailable for insufficient grants, and updates activation checks (UsageValue/Number and UsagePercentage/Percent use usage+overage; UsagePercentage derives threshold from totalGrants; BalanceValue preserves prior logic with zero-edge handling).
Consumer tests
openmeter/notification/consumer/entitlementbalancethreshold_test.go
Adds ExpectedErr to test cases, asserts ErrorIs when set, adds many invalid-entitlement cases, and updates several thresholds/values to match new logic.
Error grouping
openmeter/notification/consumer/entitlementbalancethreshold.go
Groups ErrInvalidEntitlementValue and ErrNoBalanceAvailable into a var (...) public block and updates ErrNoBalanceAvailable message text.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • openmeterio/openmeter#4162: Also modifies getNumericThreshold and related threshold activation logic; likely overlaps on total-grants handling and guards.

Suggested reviewers

  • turip
  • gergely-kurucz-konghq
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding validation for entitlement values during balance threshold event processing, which is core to all the modifications across the four files.
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.

✏️ 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/invalid-entitlement-value

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
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

🧹 Nitpick comments (1)
openmeter/notification/consumer/entitlementbalancethreshold_test.go (1)

650-655: 💤 Low value

Tiny nit: assert actual is nil in the error path too.

When ExpectedErr != nil, the test only checks the error but not the returned value. Since getActiveThresholdsWithHighestPriority always returns nil on error, adding one line keeps the test contract tight and protects against future refactors that might accidentally return a partial result.

🔧 Suggested addition
 		} else {
 			assert.ErrorIsf(t, err, test.ExpectedErr, "must return the expected error: %s", err)
+			assert.Nilf(t, actual, "must not return a value when an error is expected")
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/notification/consumer/entitlementbalancethreshold_test.go` around
lines 650 - 655, The error-path in the test for
getActiveThresholdsWithHighestPriority only asserts the error and not the
returned value; update the else branch (where test.ExpectedErr != nil) to also
assert that actual is nil (e.g., using assert.Nil or assert.Empty) so the test
verifies the function returns no result on error and prevents regressions
returning partial data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/notification/consumer/entitlementbalancethreshold.go`:
- Around line 406-408: The new validation incorrectly compares b := totalGrants
- (usage + overage) to balance without an epsilon, causing float-noise false
positives and breaking the test; change the check in
entitlementbalancethreshold.go to use the existing absoluteZero tolerance (e.g.
if b - balance > absoluteZero { return ... }) so tiny rounding differences are
ignored, and update the failing test data in entitlementbalancethreshold_test.go
(Test_GetActiveThresholdsWithHighestPriority_Error) to make
TotalAvailableGrantAmount equal to 25.0 + 2e-9 (so b == 1e-9) so the test
satisfies the new invariant while keeping the original absoluteZero boundary
assertions.

---

Nitpick comments:
In `@openmeter/notification/consumer/entitlementbalancethreshold_test.go`:
- Around line 650-655: The error-path in the test for
getActiveThresholdsWithHighestPriority only asserts the error and not the
returned value; update the else branch (where test.ExpectedErr != nil) to also
assert that actual is nil (e.g., using assert.Nil or assert.Empty) so the test
verifies the function returns no result on error and prevents regressions
returning partial data.
🪄 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: 4c35a3d0-2a6d-4e59-8235-e143ec362834

📥 Commits

Reviewing files that changed from the base of the PR and between ff379af and aaed905.

📒 Files selected for processing (2)
  • openmeter/notification/consumer/entitlementbalancethreshold.go
  • openmeter/notification/consumer/entitlementbalancethreshold_test.go

Comment thread openmeter/notification/consumer/entitlementbalancethreshold.go Outdated
@chrisgacsal chrisgacsal marked this pull request as draft May 2, 2026 08:54
@chrisgacsal chrisgacsal force-pushed the fix/invalid-entitlement-value branch from aaed905 to 8829064 Compare May 5, 2026 11:20
@chrisgacsal chrisgacsal force-pushed the fix/invalid-entitlement-value branch from 8829064 to c2d63db Compare May 5, 2026 12:06
@chrisgacsal chrisgacsal marked this pull request as ready for review May 5, 2026 13:21
Comment thread openmeter/notification/consumer/entitlementbalancethreshold.go Outdated
Copy link
Copy Markdown
Contributor

@tothandras tothandras left a comment

Choose a reason for hiding this comment

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

Reviewing.

if balance > absoluteZero && overage > absoluteZero {
return nil, errors.New("balance and overage cannot be positive number at the same time")
// TotalAvailableGrants must be equal or greater than the sum of Balance, Usage and Overage.
if balance+usage+overage < totalGrants {
Copy link
Copy Markdown
Contributor

@tothandras tothandras May 5, 2026

Choose a reason for hiding this comment

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

Comment is incorrect?

tothandras
tothandras previously approved these changes May 5, 2026
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

🧹 Nitpick comments (1)
openmeter/notification/consumer/entitlementbalancethreshold_test.go (1)

99-113: 💤 Low value

Tiny nit: this fixture is internally inconsistent — consider mirroring the sibling case.

The next case ("with overage over the total available grant amount", L114-129) properly sets Overage: 20.0 so that usage - overage matches the granted amount. This one has Usage: 100, TotalAvailableGrantAmount: 50 and no overage, which can't physically happen (you can't burn 100 from 50 grants without overage). The new loose invariant check still passes it, but it's a bit of a misleading fixture — adding Overage: lo.ToPtr(50.0) would make it model a realistic state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/notification/consumer/entitlementbalancethreshold_test.go` around
lines 99 - 113, Test fixture "Usage values only - over the total available grant
amount" has inconsistent EntitlementValue (Usage 100 with
TotalAvailableGrantAmount 50 and no Overage); update the EntitlementValue in
that case to include an Overage (e.g. set Overage via lo.ToPtr(50.0)) so that
Usage - Overage equals TotalAvailableGrantAmount, mirroring the sibling case and
keeping the fixture physically realistic; locate the case by its Name string and
modify the EntitlementValue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openmeter/notification/consumer/entitlementbalancethreshold_test.go`:
- Around line 732-745: The test case named "Invalid entitlement value - total
available grants exceeds balance+usage+overage" is incorrectly using
TotalAvailableGrantAmount: -1.0 (which triggers the negative-value guard)
instead of a positive value larger than the sum of Balance+Usage+Overage; update
the EntitlementValue in that test (the TotalAvailableGrantAmount field in the
table entry) to a non-negative number greater than Balance+Usage+Overage (e.g.,
if Balance/Usage/Overage are 0.0 set TotalAvailableGrantAmount to 1.0) so the
code path in the entitlement validation (the invariant check around the logic
that raises ErrInvalidEntitlementValue) is actually exercised.

---

Nitpick comments:
In `@openmeter/notification/consumer/entitlementbalancethreshold_test.go`:
- Around line 99-113: Test fixture "Usage values only - over the total available
grant amount" has inconsistent EntitlementValue (Usage 100 with
TotalAvailableGrantAmount 50 and no Overage); update the EntitlementValue in
that case to include an Overage (e.g. set Overage via lo.ToPtr(50.0)) so that
Usage - Overage equals TotalAvailableGrantAmount, mirroring the sibling case and
keeping the fixture physically realistic; locate the case by its Name string and
modify the EntitlementValue.
🪄 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: b53cffb9-ec32-4812-9b43-cfd3ea1509b0

📥 Commits

Reviewing files that changed from the base of the PR and between 6911d0d and 048d3ee.

📒 Files selected for processing (2)
  • openmeter/notification/consumer/entitlementbalancethreshold.go
  • openmeter/notification/consumer/entitlementbalancethreshold_test.go

Comment thread openmeter/notification/consumer/entitlementbalancethreshold_test.go Outdated
tothandras
tothandras previously approved these changes May 6, 2026
Comment thread openmeter/notification/consumer/entitlementbalancethreshold.go Outdated
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.

♻️ Duplicate comments (1)
openmeter/entitlement/snapshot/event.go (1)

134-137: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

No FP tolerance on the sum comparison — risk of false invalidations.

balance+usage+overage < totalGrants is a bare floating-point compare. When these values come out of multi-step aggregations, rounding drift can produce something like balance+usage+overage = 99.99999999 versus totalGrants = 100.0 for what should be an equal pair, which would then incorrectly raise ErrInvalidEntitlementValue and cause legitimate snapshots to be dropped. A small epsilon keeps things consistent with absoluteZero used elsewhere.

🛡️ Proposed fix (mirror the absoluteZero pattern)
-	// TotalAvailableGrants must be less than or equal to the sum of Balance, Usage and Overage.
-	if balance+usage+overage < totalGrants {
+	// TotalAvailableGrants must be less than or equal to the sum of Balance, Usage and Overage (with FP tolerance).
+	const epsilon = 1e-9
+	if totalGrants-(balance+usage+overage) > epsilon {
 		return fmt.Errorf("%w: total available grants must be less than or equal to the sum of balance, usage and overage [balance=%f, usage=%f, overage=%f, total_grants=%f]", ErrInvalidEntitlementValue, balance, usage, overage, totalGrants)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/entitlement/snapshot/event.go` around lines 134 - 137, The strict
float compare in the TotalAvailableGrants check should use the project's epsilon
tolerance to avoid false negatives; replace the bare `balance+usage+overage <
totalGrants` check in event.go with an epsilon-aware comparison using the
existing `absoluteZero` constant (e.g., compute sum := balance+usage+overage and
test `if sum < totalGrants - absoluteZero { ... }`) and keep the same error
return using `ErrInvalidEntitlementValue` so behavior is identical except for FP
tolerance.
🧹 Nitpick comments (1)
openmeter/entitlement/snapshot/event.go (1)

122-140: ⚡ Quick win

Prefer a function declaration over a mutable package-level var.

ValidateMeteredEntitlementValue is exposed as a public var holding a func, which means any importer can reassign it at runtime (e.g. snapshot.ValidateMeteredEntitlementValue = func(EntitlementValue) error { return nil }) and silently disable validation. Since the signature already matches pkgmodels.ValidatorFunc[EntitlementValue], a regular func works just as well and gives you immutability + a clean stack trace.

♻️ Proposed refactor
-var ValidateMeteredEntitlementValue = func(value EntitlementValue) error {
+func ValidateMeteredEntitlementValue(value EntitlementValue) error {
 	var (
 		balance     = lo.FromPtr(value.Balance)
 		usage       = lo.FromPtr(value.Usage)
 		overage     = lo.FromPtr(value.Overage)
 		totalGrants = lo.FromPtr(value.TotalAvailableGrantAmount)
 	)
@@
 	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/entitlement/snapshot/event.go` around lines 122 - 140, The exported
ValidateMeteredEntitlementValue is defined as a package-level var holding a func
which allows runtime reassignment; change it to a regular function declaration
"func ValidateMeteredEntitlementValue(value EntitlementValue) error" preserving
the existing logic and error returns (references: EntitlementValue,
ErrInvalidEntitlementValue, lo.FromPtr) so it cannot be mutated by importers and
retains proper stack traces.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@openmeter/entitlement/snapshot/event.go`:
- Around line 134-137: The strict float compare in the TotalAvailableGrants
check should use the project's epsilon tolerance to avoid false negatives;
replace the bare `balance+usage+overage < totalGrants` check in event.go with an
epsilon-aware comparison using the existing `absoluteZero` constant (e.g.,
compute sum := balance+usage+overage and test `if sum < totalGrants -
absoluteZero { ... }`) and keep the same error return using
`ErrInvalidEntitlementValue` so behavior is identical except for FP tolerance.

---

Nitpick comments:
In `@openmeter/entitlement/snapshot/event.go`:
- Around line 122-140: The exported ValidateMeteredEntitlementValue is defined
as a package-level var holding a func which allows runtime reassignment; change
it to a regular function declaration "func ValidateMeteredEntitlementValue(value
EntitlementValue) error" preserving the existing logic and error returns
(references: EntitlementValue, ErrInvalidEntitlementValue, lo.FromPtr) so it
cannot be mutated by importers and retains proper stack traces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79ec3647-b82e-4b6e-b4c0-15fd2bdde5db

📥 Commits

Reviewing files that changed from the base of the PR and between 048d3ee and d587f8c.

📒 Files selected for processing (4)
  • openmeter/entitlement/snapshot/event.go
  • openmeter/entitlement/snapshot/event_test.go
  • openmeter/notification/consumer/entitlementbalancethreshold.go
  • openmeter/notification/consumer/entitlementbalancethreshold_test.go

@chrisgacsal chrisgacsal force-pushed the fix/invalid-entitlement-value branch from d587f8c to b7be048 Compare May 6, 2026 13:48
@chrisgacsal chrisgacsal marked this pull request as draft May 6, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants