Skip to content

feat(budget): per-user AI spending ceilings - #132

Merged
CybotTM merged 2 commits into
mainfrom
feature/per-user-budget-tracking
Apr 22, 2026
Merged

feat(budget): per-user AI spending ceilings#132
CybotTM merged 2 commits into
mainfrom
feature/per-user-budget-tracking

Conversation

@CybotTM

@CybotTM CybotTM commented Apr 22, 2026

Copy link
Copy Markdown
Member

Summary

Adds a new dimension of AI cost control: per-backend-user budgets independent of per-configuration limits.

  • New table tx_nrllm_user_budget keyed uniquely on be_user with six independent ceilings (requests / tokens / cost × daily / monthly; 0 = unlimited on that axis)
  • UserBudget domain model + UserBudgetRepository
  • BudgetService::check(beUserUid, plannedCost) as a pre-flight check — returns BudgetCheckResult naming which bucket tripped (if any)
  • TCA + EN/DE labels for the new table (editable via List module under Admin Tools)
  • Ceilings, not counters: actual usage is aggregated on demand from tx_nrllm_service_usage, so there's no second-write per request and no drift between counters

Resolution order

  1. Uid ≤ 0 → allowed (CLI / scheduler / frontend)
  2. No budget record → allowed
  3. is_active = false → allowed
  4. All limits zero → allowed
  5. Daily bucket evaluated first, then monthly — first to trip wins; +1 request and +plannedCost are added before comparison so a user at exactly the limit still gets one more call

Scope note

Ships the table + service + primitive. Wiring BudgetService::check() into each feature service is a deliberate follow-up, same pattern as ADR-023.

Relation to existing per-configuration limits

Orthogonal and complementary:

  • Per-configuration max_*_per_day caps a preset
  • Per-user budgets cap a person across every preset
  • Both checks must pass

Test plan

  • 18 unit tests (BudgetService + BudgetCheckResult + UserBudget)
  • PHPStan level 10 clean
  • Architecture + CGL + Rector clean

See ADR-025 for design rationale and the counter-table alternative we ruled out.

Introduce tx_nrllm_user_budget keyed uniquely on be_user with six
independent ceilings — requests, tokens, cost, each daily and monthly.
Zero on any axis means unlimited on that axis.

BudgetService::check(beUserUid, plannedCost) is a pre-flight check:
fetches the user's budget row, aggregates actual usage from the
existing tx_nrllm_service_usage on demand (no second-write per request,
no counter drift), and returns a BudgetCheckResult naming the first
bucket to trip. Daily checks take precedence over monthly. A user at
exactly the limit is still allowed one more call because the incoming
request's +1 / +plannedCost is added before comparison.

This complements (does not replace) the existing per-configuration
daily limits on tx_nrllm_configuration — configuration limits cap a
preset, user budgets cap a person; both checks must pass.

Like ADR-023 (capability permissions), this ADR ships the primitive
only. Wiring BudgetService::check() into feature services is a
deliberate follow-up to keep this PR narrow.

See ADR-025 for rules, scope, and the counter-table alternative we
ruled out.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Copilot AI review requested due to automatic review settings April 22, 2026 12:11
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests Test-related changes configuration Configuration changes services Service layer changes labels Apr 22, 2026
@github-actions

github-actions Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

github-actions[bot]
github-actions Bot previously approved these changes Apr 22, 2026

@github-actions github-actions Bot left a comment

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.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.70588% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.02%. Comparing base (dc656a5) to head (611be13).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
Classes/Service/BudgetService.php 65.09% 37 Missing ⚠️
Classes/Domain/Repository/UserBudgetRepository.php 0.00% 11 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #132      +/-   ##
============================================
- Coverage     93.49%   93.02%   -0.48%     
- Complexity     2218     2259      +41     
============================================
  Files            89       92       +3     
  Lines          8150     8286     +136     
============================================
+ Hits           7620     7708      +88     
- Misses          530      578      +48     
Flag Coverage Δ
unit 93.02% <64.70%> (-0.48%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Classes/Domain/DTO/BudgetCheckResult.php 100.00% <100.00%> (ø)
Classes/Domain/Repository/UserBudgetRepository.php 0.00% <0.00%> (ø)
Classes/Service/BudgetService.php 65.09% <65.09%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds per-backend-user budget ceilings (daily/monthly requests, tokens, and cost) as a new cost-control layer orthogonal to per-configuration limits, backed by on-demand aggregation from tx_nrllm_service_usage.

Changes:

  • Introduces tx_nrllm_user_budget table + TCA + EN/DE labels for managing per-user ceilings in the TYPO3 backend.
  • Adds UserBudget model, UserBudgetRepository, BudgetService::check() and BudgetCheckResult to evaluate daily then monthly windows.
  • Adds unit tests covering BudgetService behavior and BudgetCheckResult factories/formatting, plus ADR-025 documentation.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
ext_tables.sql Adds schema for tx_nrllm_user_budget with unique be_user and daily/monthly ceiling fields.
Configuration/TCA/tx_nrllm_user_budget.php Defines backend editing UI (tabs/palettes/fields) for user budget records.
Resources/Private/Language/locallang_tca.xlf Adds EN labels for the new table/fields/tabs.
Resources/Private/Language/de.locallang_tca.xlf Adds DE labels for the new table/fields/tabs.
Configuration/Services.yaml Registers UserBudgetRepository and BudgetService as public services.
Classes/Domain/Model/UserBudget.php Adds the domain model encapsulating budget ceiling fields and helper hasAnyLimit().
Classes/Domain/Repository/UserBudgetRepository.php Adds repository lookup by backend user; disables storage-page filtering.
Classes/Service/BudgetService.php Implements the pre-flight budget check by aggregating usage and comparing against ceilings.
Classes/Domain/DTO/BudgetCheckResult.php Adds a DTO for allowed/denied results with limit identifier + reason/current usage/limit values.
Tests/Unit/Service/BudgetServiceTest.php Unit tests for daily/monthly evaluation order, limit tripping rules, and plannedCost handling.
Tests/Unit/Domain/DTO/BudgetCheckResultTest.php Unit tests for factories, reason generation, and formatting behavior.
Documentation/Adr/Index.rst Adds ADR-025 to ADR index.
Documentation/Adr/Adr025PerUserBudgets.rst Documents the decision, rules, scope, and alternatives for per-user budgets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Classes/Domain/Repository/UserBudgetRepository.php
Comment thread Classes/Service/BudgetService.php
Comment thread Classes/Service/BudgetService.php Outdated
Comment thread Classes/Service/BudgetService.php Outdated
Comment thread Classes/Domain/DTO/BudgetCheckResult.php Outdated
Comment thread Resources/Private/Language/locallang_tca.xlf Outdated
- Register UserBudget persistence mapping in Configuration/Extbase/
  Persistence/Classes.php. Without this the repository was querying
  a non-existent tx_nrllm_domain_model_userbudget (Extbase default
  convention) instead of tx_nrllm_user_budget.
- Clamp negative plannedCost to 0 at BudgetService::check() entry.
  A negative value could otherwise artificially reduce the projected
  total and let callers bypass cost limits.
- Combine daily + monthly aggregates into ONE DB roundtrip via
  conditional SUM() expressions. Replaces two per-request queries
  with one when both windows are configured. `aggregateUsage` hook
  renamed to `aggregateWindowUsage` with the new signature; tests
  updated to stub the new method.
- Document the pre-flight race-condition (two concurrent requests
  can both pass the check before either updates the usage table)
  in the class docblock. Full serialisation would hot-path every
  AI request, so it stays best-effort.
- BudgetCheckResult::denied() now renders a human-friendly reason
  ("AI budget exhausted: monthly cost is at ..."). Internal limit
  identifier remains available as the stable machine key on
  $exceededLimit.
- Backend labels for tx_nrllm_user_budget aligned with the existing
  tx_nrllm_configuration style ("Max Requests/Day" instead of
  "Max requests per day"). EN + DE updated.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>

@github-actions github-actions Bot left a comment

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.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@CybotTM
CybotTM added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 9f25dd9 Apr 22, 2026
42 checks passed
@CybotTM
CybotTM deleted the feature/per-user-budget-tracking branch April 22, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

configuration Configuration changes documentation Improvements or additions to documentation services Service layer changes tests Test-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants