[AUTO-BUG-5] SessionCostBudget.record_cost() does not enforce budget cap — costs silently accumulate beyond max_cost_usd limit #10479

Open
opened 2026-04-18 10:04:52 +00:00 by HAL9000 · 0 comments
Owner

Metadata

Field Value
Branch bugfix/m6-session-cost-budget-record-cost-enforcement
Commit Message fix(domain/cost_budget): enforce max_cost_usd cap in SessionCostBudget.record_cost() to prevent silent budget overruns
Milestone v3.5.0

Background and Context

The SessionCostBudget class in src/cleveragents/domain/models/core/cost_budget.py is documented as enforcing a configurable maximum cost cap:

"Each tier independently tracks accumulated costs and enforces a configurable maximum. The tightest limit at any level wins: if a tool invocation would exceed any tier's budget, the request is denied."

The class also provides a would_exceed() method (lines 115–128) that correctly checks whether adding a cost would exceed the budget. However, the record_cost() method (lines 140–151) never calls would_exceed() — it simply adds the cost unconditionally:

def record_cost(self, cost: float) -> None:
    """Add *cost* to the running total.

    Raises:
        TypeError: If *cost* is not numeric.
        ValueError: If *cost* is negative.
    """
    if not isinstance(cost, (int, float)):
        raise TypeError("cost must be a number")
    if cost < 0:
        raise ValueError("cost must be non-negative")
    self.total_cost += cost  # ← No enforcement of max_cost_usd!

This violates the specification requirement that "Business rules must be enforced in domain layer, not application layer." The budget cap is a domain business rule, but record_cost() delegates enforcement to callers (application layer), which may forget to call would_exceed() first.

Current Behavior

from cleveragents.domain.models.core.cost_budget import SessionCostBudget

budget = SessionCostBudget(max_cost_usd=10.0, total_cost=9.0)

# BUG: This should raise an exception — adding 5.0 would bring total to 14.0, exceeding the 10.0 cap
budget.record_cost(5.0)

# No exception raised; total_cost is now 14.0, silently exceeding the cap
assert budget.total_cost == 14.0  # True — budget was silently exceeded
assert budget.is_exceeded()  # True — but no exception was raised during record_cost

Expected Behavior

record_cost() should enforce the budget cap at the domain level. When adding the specified cost would cause total_cost to exceed max_cost_usd, record_cost() must raise an appropriate exception before modifying total_cost.

The corrected behavior:

budget = SessionCostBudget(max_cost_usd=10.0, total_cost=9.0)
budget.record_cost(5.0)  # Should raise — 9.0 + 5.0 = 14.0 > 10.0
# Expected: raises ValueError or a domain-specific BudgetExceededError

When max_cost_usd is None (unlimited), record_cost() should continue to accept any non-negative cost.

Impact

  • Spec violation: The specification states "Business rules must be enforced in domain layer, not application layer." The budget cap is a domain business rule that is currently enforced only if callers remember to call would_exceed() first.
  • Silent budget overruns: Any caller that calls record_cost() without first checking would_exceed() will silently exceed the budget cap, potentially causing unexpected cost overruns.
  • Inconsistent contract: The class docstring promises enforcement ("the request is denied"), but record_cost() does not deliver on this promise.

Acceptance Criteria

  • SessionCostBudget.record_cost() raises an appropriate exception when adding the cost would exceed max_cost_usd
  • total_cost is not modified when the exception is raised (atomic — either succeeds or fails)
  • When max_cost_usd is None, record_cost() continues to accept any non-negative cost
  • The same fix is applied to OrgCostAccumulator.record_cost() (lines 244–255) which has the identical bug
  • The TDD scenario from issue #10467 passes after the fix is applied
  • All existing cost budget tests continue to pass
  • Coverage ≥ 97%

Subtasks

  • Read SessionCostBudget.record_cost() in src/cleveragents/domain/models/core/cost_budget.py (lines 140–151)
  • Add budget enforcement to SessionCostBudget.record_cost(): call self.would_exceed(cost) and raise ValueError (or a new domain-specific exception) if it returns True
  • Apply the same fix to OrgCostAccumulator.record_cost() (lines 244–255)
  • Verify ThreadSafeOrgCostAccumulator.record_cost() (line 328) inherits the fix correctly via delegation
  • Remove @tdd_expected_fail tag from the TDD scenario in issue #10467 after the fix is applied
  • Run nox -s unit_tests to confirm all tests pass
  • Verify coverage ≥ 97% via nox -s coverage_report
  • Run nox (all default sessions), fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation.
  • The commit is pushed to the remote on the branch bugfix/m6-session-cost-budget-record-cost-enforcement.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.

Duplicate Check

Searched open issues (pages 1–3, 50 per page) and closed issues (pages 1–3, 50 per page) for keywords: record_cost, budget enforce, cost budget enforce, SessionCostBudget, max_cost_usd. No existing issues found covering this specific bug.


Automated by CleverAgents Bot
Agent: new-issue-creator

## Metadata | Field | Value | |---|---| | **Branch** | `bugfix/m6-session-cost-budget-record-cost-enforcement` | | **Commit Message** | `fix(domain/cost_budget): enforce max_cost_usd cap in SessionCostBudget.record_cost() to prevent silent budget overruns` | | **Milestone** | v3.5.0 | ## Background and Context The `SessionCostBudget` class in `src/cleveragents/domain/models/core/cost_budget.py` is documented as enforcing a configurable maximum cost cap: > "Each tier independently tracks accumulated costs and enforces a configurable maximum. The **tightest** limit at any level wins: if a tool invocation would exceed *any* tier's budget, the request is denied." The class also provides a `would_exceed()` method (lines 115–128) that correctly checks whether adding a cost would exceed the budget. However, the `record_cost()` method (lines 140–151) **never calls `would_exceed()`** — it simply adds the cost unconditionally: ```python def record_cost(self, cost: float) -> None: """Add *cost* to the running total. Raises: TypeError: If *cost* is not numeric. ValueError: If *cost* is negative. """ if not isinstance(cost, (int, float)): raise TypeError("cost must be a number") if cost < 0: raise ValueError("cost must be non-negative") self.total_cost += cost # ← No enforcement of max_cost_usd! ``` This violates the specification requirement that "Business rules must be enforced in domain layer, not application layer." The budget cap is a domain business rule, but `record_cost()` delegates enforcement to callers (application layer), which may forget to call `would_exceed()` first. ## Current Behavior ```python from cleveragents.domain.models.core.cost_budget import SessionCostBudget budget = SessionCostBudget(max_cost_usd=10.0, total_cost=9.0) # BUG: This should raise an exception — adding 5.0 would bring total to 14.0, exceeding the 10.0 cap budget.record_cost(5.0) # No exception raised; total_cost is now 14.0, silently exceeding the cap assert budget.total_cost == 14.0 # True — budget was silently exceeded assert budget.is_exceeded() # True — but no exception was raised during record_cost ``` ## Expected Behavior `record_cost()` should enforce the budget cap at the domain level. When adding the specified cost would cause `total_cost` to exceed `max_cost_usd`, `record_cost()` must raise an appropriate exception before modifying `total_cost`. The corrected behavior: ```python budget = SessionCostBudget(max_cost_usd=10.0, total_cost=9.0) budget.record_cost(5.0) # Should raise — 9.0 + 5.0 = 14.0 > 10.0 # Expected: raises ValueError or a domain-specific BudgetExceededError ``` When `max_cost_usd` is `None` (unlimited), `record_cost()` should continue to accept any non-negative cost. ## Impact - **Spec violation**: The specification states "Business rules must be enforced in domain layer, not application layer." The budget cap is a domain business rule that is currently enforced only if callers remember to call `would_exceed()` first. - **Silent budget overruns**: Any caller that calls `record_cost()` without first checking `would_exceed()` will silently exceed the budget cap, potentially causing unexpected cost overruns. - **Inconsistent contract**: The class docstring promises enforcement ("the request is denied"), but `record_cost()` does not deliver on this promise. ## Acceptance Criteria - [ ] `SessionCostBudget.record_cost()` raises an appropriate exception when adding the cost would exceed `max_cost_usd` - [ ] `total_cost` is not modified when the exception is raised (atomic — either succeeds or fails) - [ ] When `max_cost_usd` is `None`, `record_cost()` continues to accept any non-negative cost - [ ] The same fix is applied to `OrgCostAccumulator.record_cost()` (lines 244–255) which has the identical bug - [ ] The TDD scenario from issue #10467 passes after the fix is applied - [ ] All existing cost budget tests continue to pass - [ ] Coverage ≥ 97% ## Subtasks - [ ] Read `SessionCostBudget.record_cost()` in `src/cleveragents/domain/models/core/cost_budget.py` (lines 140–151) - [ ] Add budget enforcement to `SessionCostBudget.record_cost()`: call `self.would_exceed(cost)` and raise `ValueError` (or a new domain-specific exception) if it returns `True` - [ ] Apply the same fix to `OrgCostAccumulator.record_cost()` (lines 244–255) - [ ] Verify `ThreadSafeOrgCostAccumulator.record_cost()` (line 328) inherits the fix correctly via delegation - [ ] Remove `@tdd_expected_fail` tag from the TDD scenario in issue #10467 after the fix is applied - [ ] Run `nox -s unit_tests` to confirm all tests pass - [ ] Verify coverage ≥ 97% via `nox -s coverage_report` - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation. - The commit is pushed to the remote on the branch `bugfix/m6-session-cost-budget-record-cost-enforcement`. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. ## Duplicate Check Searched open issues (pages 1–3, 50 per page) and closed issues (pages 1–3, 50 per page) for keywords: `record_cost`, `budget enforce`, `cost budget enforce`, `SessionCostBudget`, `max_cost_usd`. No existing issues found covering this specific bug. --- **Automated by CleverAgents Bot** Agent: new-issue-creator
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#10479
No description provided.