ADR-232: An editor's budget status is public API
- Status
-
Accepted
- Date
-
2026-10-28
- Authors
-
Netresearch DTT GmbH
Context
The AI Tasks module shows an editor their own usage today and this month
against the limits of their budget record. One read model produces it:
User returns a User
of two Budget objects with three Budget each,
formatted by Localized in the reader's locale. All five
classes were @internal.
Other extensions show the same status to the same editor. The cowriter
(netresearch/) does it in its dialog and calls
for and Localized
directly. Under @internal these may change in any release, including a
patch release, and for already changed its signature between
0.41.8 and 0.41.9. A consumer that accepts every patch release of a minor
line would then break without a version conflict.
Decision
The five classes are @api (ADR-127):
Netresearch\, the entry point. Consumers obtain it from the DI container; its constructor is frozen in the snapshot but out of contract, and its parameter types (the repository and the usage-window reader) stayNr Llm\ Service\ Budget\ User Budget Status Service @internal, as API stability states for every service.Netresearch\,Nr Llm\ Domain\ Value Object\ User Budget Status BudgetandWindow Status Budget, the value objectsMeter forreturns, including theirUser () toshapes.Array () Netresearch\, whichNr Llm\ Service\ Formatting\ Localized Number Formatter fortakes andUser () Budgetcarries.Meter
This is the signature-transitive closure of for: no further
Netresearch\ type appears in a method or property signature of the
five. The snapshot test asserts that.
Nothing changes in behaviour or signatures. The change is additive: five
classes are added to Tests/.
Consequences
- Within a minor line the read model keeps its signatures and its
toshapes; during 0.x a break needs a minor release and a BREAKING changelog entry.Array () Userhas no interface. A consumer that wants to replace it in a test builds aBudget Status Service Useritself, which the value-object constructors allow.Budget Status BudgetandMeter::$formatter Budgetare part of the surface.Window Status:: from Usage () fromtakes the usage array in the shape the internal usage-window reader returns; that shape is now frozen with it.Usage ()
Alternatives considered
- A new facade that returns plain arrays. Consumers would get a second read path next to the one the AI Tasks module uses, and the two could drift. Promoting the existing model keeps one.
- Keeping the classes internal and asking consumers to pin a patch release. That moves the cost to every consumer and still gives no promise.