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, SpeechSynthesisOptions and ImageGenerationOptions implement BudgetAwareOptionsInterface via BudgetFieldsTrait: optional trailing beUserUid / plannedCost constructor parameters, fromArray() keys of the same names, validation rejecting negative values. The fields stay out of toArray() — the services build their wire payload from toArray(), so a missed exclusion would leak the uid to the remote API.
  • WhisperTranscriptionService, TextToSpeechService and DallEImageService forward getBeUserUid() to their trackUsage() calls.
  • FalImageService keeps its plain options array (no DTO exists) and reads a documented beUserUid key — the same array pattern TranslationService uses for translators. The payload builder is an explicit allowlist, so the key never reaches the FAL API.
  • DallEImageService::createVariations() and edit() take no options object; they gain an optional trailing ?int $beUserUid scalar 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. plannedCost is 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.