ADR-098: Input-guardrail screening for specialized prompts
- Status
-
Accepted
- Date
-
2026-07-21
- Authors
-
Netresearch DTT GmbH
Context
The chat send path screens an outgoing message list through the input
guardrails before it reaches a provider (ADR-087), so a REDACT
verdict rewrites a secret out of the prompt and a DENY / REQUIRE_APPROVAL throws
before the call. The output guardrails run inside the pipeline
(ADR-085), but they only ever see a model-generated
Completion — the prompt payload is not reachable there, which is why
input screening runs on the send path.
The specialized services — DALL·E, FAL, TTS, DeepL — now route their dispatch
through the pipeline (ADR-097), but the pipeline still cannot
screen their input: a specialized call sends a prompt string, not a
Completion, and a middleware could not rewrite that prompt back into
the terminal closure a REDACT verdict requires. So the same user-supplied text
that a chat message would have had screened reached the image / speech /
translation providers unscreened.
Decision
Input gains screen — the single-
string sibling of screen — sharing one private
screen loop so both apply identical guardrails and verdict handling.
Abstract takes the Input as a required
dependency (after pipeline) and exposes `protected screen`. Each service screens its user-supplied prompt on the send path, before
the request payload is built:
- DALL·E —
generate,generate(DALL·E 2 branch; the DALL·E 3 branch screens via its delegation toMultiple generate),() edit. - FAL —
generate,generate.Multiple - TTS —
synthesize(synthesize/To File synthesizedelegate to it, so every chunk is screened).Long - DeepL —
translate,translate(each element).Batch
Whisper is out of scope: its payload is audio, not a prompt. The optional transcription-hint field is not screened here — if that becomes a concern it is its own step.
Consequences
- Breaking:
Abstractgained a requiredSpecialized Service Inputconstructor parameter (afterGuardrail Screener pipeline, before the optional repositories). A subclass or manual construction must pass it; anInputwith no guardrails is a valid pass-through for tests that do not exercise screening. Required, not optional, for the same reason as the budget gate (ADR-078): a secret / injection screener that silently disappears when unwired is fail-open on exactly the control it provides.Guardrail Screener ( []) - A specialized prompt is now screened identically to a chat prompt: a REDACT verdict rewrites the text that is sent (and, where the service echoes the prompt back in its result, what it echoes), and a DENY / REQUIRE_APPROVAL throws the same typed exception before any spend — every service screens the prompt before its budget pre-flight, so a denied prompt costs nothing: no budget-aggregation queries, no dispatch. A REDACT verdict that carries no replacement text fails closed (throws) rather than passing the original through, on the specialized and the chat path alike.
- Production wiring is unchanged: DI autowires the concrete
Inputinto every specialized service. With no input guardrails tagged,Guardrail Screener screenis a pass-through and behaviour is identical to before.Text () - Still deferred: the fail-closed dispatch seam (
getthrowing when no lifecycle context is active); and folding the per-service usage recording into a tagged pipeline extractor.Secure Client ()