The advisory pipeline
AdvisoryClient::advise() is the single governance pipeline every AI interaction passes through. This page
traces every stage so you know exactly where each behavior lives and which Advisory each branch returns.
Signature
public function advise(
string $task, // audit label, e.g. 'access_explain'
string $system, // system prompt
string $userPrompt, // user question (redacted before use)
array $evidence, // real facts the model may cite (redacted)
array $allowedRefs, // identifiers the output may cite (guard whitelist)
string $deterministicFallback, // the non-AI answer, always available
): Advisory;
The full flow
Stage 1 — mandatory redaction (pre)
Before anything else, the client resets the shared Redactor’s didRedact flag, then redacts the prompt
and recursively redacts the evidence. This happens regardless of enabled — even the deterministic path
records whether redaction would have fired. Nothing un-redacted is ever assembled into a prompt.
→ PRE-prompt redaction
Stage 2 — the enabled gate
if (! $this->enabled()) { /* return deterministic Advisory */ }
If config('iam-ai.enabled') is false (the default), the client returns immediately with the
deterministicFallback, aiUsed = false, guardPassed = true, provider = 'deterministic'. The transport
is never touched — which is why the default is sovereign even if a provider is misconfigured.
Stage 3 — transport
The client builds the user message as the redacted prompt plus a JSON evidence block prefixed with
“cite only these references”, then calls $this->provider->complete($system, $fullUser). The provider is the
abstract AiProvider; what’s bound depends on config. Any thrown Throwable is caught and converted to a
deterministic Advisory (aiUsed = false, provider = <name>) — a transport failure is never an opaque
error to the user. → Sovereign by default
Stage 4 — the hallucination guard
The raw output is checked against allowedRefs. A non-empty violation set discards the model text and returns
the fallback with aiUsed = true, guardPassed = false, and the offending IDs in violations. A clean
output proceeds. → The hallucination guard
Stage 5 — output redaction (defense-in-depth)
Only on the clean path: the model’s output is redacted again before it is returned or audited, in case the
model reflected PII/secrets that slipped the input redaction. The returned redacted flag is the OR of the
input and output passes.
Stage 6 — audit (every path)
Every branch funnels through record() before returning, writing stream=ai / iam.ai.advisory with the
governance metadata (and the sanitized output only if store_outputs=true). No Advisory escapes unaudited.
→ Audit & privacy
What each branch returns
| Branch | text |
aiUsed |
guardPassed |
provider |
|---|---|---|---|---|
| AI disabled (default) | fallback | false |
true |
deterministic |
| Transport threw | fallback | false |
true |
provider name |
| Guard found violations | fallback | true |
false |
provider name |
| Clean success | ρ(output) |
true |
true |
provider name |
Invariants
Always an Advisory
Every path constructs and records an Advisory. There is no exception thrown to the caller for a transport
failure and no null return — the deterministic fallback guarantees a usable answer.
Redaction precedes transmission
The redaction stage runs before the prompt is assembled; the transport only ever sees redacted text. The
output is redacted again before return/audit.
The model never decides
On no branch does the model’s output become an enforcement signal. The worst a bad output can do is be
discarded in favor of the deterministic fallback.