ADR-057: Speech and image services carry attribution in options
- Status
-
Accepted
- Date
-
2026-07-14
- Authors
-
Netresearch DTT GmbH
Context
ADR-052 made the caller-supplied beUserUid win
over the ambient backend.user aspect for usage attribution, but
deferred the four specialized speech/image services: their option
shapes (TranscriptionOptions, SpeechSynthesisOptions,
ImageGenerationOptions, and FalImageService's plain array)
carried no budget fields, so every transcription, synthesis and image
generation landed in the ambient bucket — be_user = 0 for
frontend, CLI and worker callers — with no way for a consumer to
attribute the spend.
Unlike chat/completion/embedding/vision, these services do not run
through the middleware pipeline (they dispatch HTTP directly via
AbstractSpecializedService), so neither BudgetMiddleware nor
UsageMiddleware can supply the uid; the services call
trackUsage() themselves.
Decision
Extend the options-based attribution of ADR-052 to the specialized services — attribution only, no enforcement:
TranscriptionOptions,SpeechSynthesisOptionsandImageGenerationOptionsimplementBudgetAwareOptionsInterfaceviaBudgetFieldsTrait: optional trailingbeUserUid/plannedCostconstructor parameters,fromArray()keys of the same names, validation rejecting negative values. The fields stay out oftoArray()— the services build their wire payload fromtoArray(), so a missed exclusion would leak the uid to the remote API.WhisperTranscriptionService,TextToSpeechServiceandDallEImageServiceforwardgetBeUserUid()to theirtrackUsage()calls.FalImageServicekeeps its plain options array (no DTO exists) and reads a documentedbeUserUidkey — the same array patternTranslationServiceuses for translators. The payload builder is an explicit allowlist, so the key never reaches the FAL API.DallEImageService::createVariations()andedit()take no options object; they gain an optional trailing?int $beUserUidscalar parameter instead of growing a new options type for two DALL-E-2-only endpoints.
Consequences
- Consumers can attribute speech and image spend per backend user from any context; without the uid the ambient fallback keeps the previous behaviour.
- Attribution and enforcement remain decoupled: the specialized
services still bypass the middleware pipeline, so per-user budget
ceilings are NOT enforced on speech/image calls.
plannedCostis carried but unused there. Routing these services through a budget pre-flight is a separate decision with its own trade-offs (no token-based cost model for FAL, multipart request flows) and gets its own ADR if a consumer needs it. - The three option DTOs and the two DALL-E signatures are public
surface; the additions are optional trailing parameters
(semver-minor in the 0.x line, same policy as the ADR-052
trackUsage()change). BudgetAwareOptionsInterface's docblock now names the attribution-only consumers so the "reaches BudgetMiddleware" assumption is not silently wrong.