# Verdicts, risk and reason codes

This page covers the vocabulary a Doberman decision is made of: the verdict, the risk level, and the reason codes and explanation that justify anything short of a plain pass.

Every decision resolves to one of three verdicts: `PASS`, `AUTH`, `BLOCK`. These aren't independent flags. They form a total order, `PASS < AUTH < BLOCK`, and that order is the whole safety model in one sentence: nothing in the [decision path](https://docs.trydoberman.dev/concepts/decision-path/) is allowed to move a verdict down it. `combine()` takes the higher of two verdicts every time it merges guardrail results; a guardrail can escalate an action another guardrail passed, never quietly wave through one that another flagged.

Risk is a separate, parallel scale, `low`, `medium`, `high`, `critical`, carried on both the individual guardrail result and the normalized action, and raised the same way the verdict is: by taking the max. Risk isn't the whole picture of how dangerous an action is, though. Two more dimensions travel alongside it: reversibility (`low`/`medium`/`high`) describes how easily the action's effects can be undone, and blast radius (`single`/`few`/`many`/`mass`/`unknown`), part of the action's algebra classification, describes how many entities it touches. The subjective layer weighs these when it decides whether an action deserves a step-up.

A `GuardrailResult` is what one guardrail hands back for one action: a verdict, a risk level, a list of reason codes, and a one-line explanation. It's immutable, and it's validated the moment it's built: any verdict other than `PASS` is rejected unless it carries at least one reason code and a non-empty explanation. There's no code path where an `AUTH` or `BLOCK` reaches you unexplained.

Reason codes are shared constants, not free text: `secret_exfiltration`, `destructive_command`, `unusual_for_deployment`, `lethal_trifecta`, 57 in total, each `snake_case`, each documented with the module that raises it and what it means. See the full [reason codes](https://docs.trydoberman.dev/reference/reason-codes/) catalogue. The explanation string attached to a code is written to describe the rule, not the payload: "path is under a protected directory," never the path itself, and never the matched secret or command. That is a hard rule for anyone writing a guardrail.

Paths get one more layer of care before any rule compares them. A single canonicalize helper resolves `.`, `..`, and symlinks, and confines the result to the repo root. Every path-matching rule in Doberman goes through it, so there's no second comparison path a `..` or a symlink could slip past.

```mermaid
flowchart TB
  SO["SecurityObject<br/><i>normalized action</i>"]
  AT["ActionType<br/>file_write · shell_exec ·<br/>network_request · …"]
  RK["Risk<br/>score + BlastRadius +<br/>Reversibility"]
  EC["EvalContext<br/>repo root · role · mode ·<br/>session metadata"]
  GR["GuardrailResult<br/>verdict + reason_codes +<br/>explanation"]
  V["Verdict<br/>PASS < AUTH < BLOCK<br/><i>total order</i>"]
  RC["ReasonCode<br/>shared constants<br/>(57 documented)"]
  D["Decision<br/>final record, redacted"]
  TO["TurnObject<br/>(turn gate input)"]
  AL["Algebra<br/>action classification"]
  SO --- AT
  SO --- RK
  SO --- AL
  GR --- V
  GR --- RC
  EC -- "context for" --> GR
  GR -- "combined into" --> D
  TO -. "parallel vocabulary<br/>for turns" .- SO
  classDef god fill:#a97e4f,color:#fff,stroke:#a97e4f
  class SO,V god
```
`SecurityObject` and `Verdict` are the most-connected symbols in the codebase; a change to either is a whole-system change.
