ADR-079: First-party fakes for Completion, Vision and Budget services
- Status
-
Accepted
- Date
-
2026-07-17
- Authors
-
Netresearch DTT GmbH
Context
ADR-073 shipped maintained, first-party test doubles for
the tool-calling and embedding feature services under the runtime-autoloaded
Netresearch\ namespace, because consumers were hand-rolling
doubles that fatal the moment the interface grows. It deliberately deferred
the remaining feature interfaces: "Other feature interfaces (completion,
vision, translation) gain a first-party fake only when a consumer needs one."
That consumer now exists. nr_ (podcast/diagram/story generation)
consumes Completion and Vision and gates image/speech
spend through Budget — and its hand-written
Budget double is exactly the one that broke across
consumers when check gained its ?Llm parameter (the
0.20 signature change), the failure ADR-073 exists to prevent.
Decision
Ship three more first-party fakes, extending ADR-073 to the surfaces a concrete consumer now uses:
Fake— the sixCompletion Service Completion-returning methods (Response complete,complete,Factual completeand theirCreative *Fortwins from ADR-077) draw from a FIFOConfiguration $responsesqueue;completeandJson* completereturn canned properties. Every call is recorded.Markdown* Fake— the four string-or-array methods return the canned string echoing the caller's arity (single image → string, batch → one per input);Vision Service analyzereturns a cannedImage Full Vision.Response Fake—Budget Service checkreturns a canned() Budget(allowing by default) and records every call.Check Result
Each is a plain final class (not readonly — the canned-value and
call-recording properties are set by tests) implementing the real interface,
so PHPStan level 10 keeps the double in lock-step with the production
contract. Each carries a one-shot $throwable to exercise a consumer's
error path.
Beyond ADR-073's named trio, this includes Budget (not a
Service\ interface, but the one whose signature drift concretely
broke consumers) and omits translation (no consumer needs it yet — the
same demand-pull rule ADR-073 set).
Consequences
- Consumers type-hint their unit tests against a maintained fake instead of a hand-rolled double; a future signature change fails the build here rather than silently in every consumer.
- The three interfaces each gain a second implementor (the fake). A future signature change now has a production + fake blast radius — intended: the fake failing PHPStan is the guardrail.
- Purely additive and DI-neutral:
Classes/is already excluded from container autoconfiguration (Testing/* Configuration/) and covered by the production PSR-4 autoload, so the public-service count (ADR-028) is unchanged and consumers get the doubles without nr_llm dev dependencies.Services. yaml - The
Testing\surface (property and method names) is a supported public API, as ADR-073 established: renames are breaking changes and belong in release notes.