# Policy and the drift gate

This page explains what a Doberman policy is, how you tune it, and why loosening it is deliberately harder than tightening it.

A policy is the checklist Doberman evaluates every action against: which rules are on, how sensitive the engine is to unusual behavior, and what counts as an acceptable risk in this repo. It lives in `.doberman/policies.yaml`, a plain YAML file scoped to the project it protects. You rarely hand-edit it. `doberman review` shows the recommended checklist and, with `--yes`, saves it; `doberman mode`, `doberman prefs`, and `doberman enforcement` change specific dials without you touching the file directly.

## Strength modes

One dial controls how sensitive the engine is: Light, Balanced, Strict, or Paranoid, set with `doberman mode <name>`. Balanced is the default. Stricter modes lower the thresholds that trigger a step-up to `AUTH`, so you see more prompts as you move toward Paranoid. They never change what counts as a hard block. Doberman's core protections, secret exfiltration, destructive commands, and the rest, run the same in every mode, Light included.

Alongside the mode sits a preference vector: four independent weights, `confidentiality`, `reversibility`, `interruption_tolerance`, and `blast_radius`, tuned with `doberman prefs <dimension> <value>`. These weights shape how the subjective layer decides when an unusual action deserves a step-up. They cannot loosen the objective floor. A deployment that never wants to be asked twice about the same low-risk category can turn that dimension down without touching secret detection at all.

A third setting, egress velocity thresholds (burst, byte volume, fan-out), controls when the runtime egress broker treats outbound traffic as abnormal. See [Egress broker](https://docs.trydoberman.dev/concepts/egress-broker/) for how those numbers get used.

## The drift gate

Every one of these settings can move in two directions, and only one of those directions is free. `drift.py` is the single chokepoint every policy change passes through, whether it comes from the CLI or the local dashboard. It classifies a proposed change by comparing it to the *current effective* state, not to Doberman's built-in defaults. That distinction matters: if you tightened Strict a week ago and now ask to go back to Balanced, that is a weaken relative to where you are today, even though Balanced is the shipped default.

> **Fail closed** A change classified as a strengthen applies immediately, no friction. A change classified as a weaken requires a possession factor: a TOTP code if you have 2FA enrolled, otherwise your local Doberman password. Refuse the factor, or have none enrolled, and the change is denied and the policy state is unchanged.

Every attempt is written to an append-only ledger, `doberman policy-history`, including the ones that were denied. A string of refused weaken attempts is itself a signal worth noticing: it can mean an agent, or something driving it, is probing for a way to loosen its own guardrails.

Both entry points, the `mode`/`prefs`/`enforcement` CLI commands and the dashboard's mode control, call the same `apply_mode_change` path. Neither can bypass the classification or the gate; there is no second code path that skips the possession-factor check.

```mermaid
flowchart TB
  CH["checklist.py<br/>PolicyDoc (YAML in .doberman/)"]
  MO["modes.py<br/>Light · Balanced · Strict · Paranoid"]
  PR["preferences.py"]
  VT["egress velocity thresholds<br/>(policy-configurable)"]
  CH --- MO
  CH --- PR
  CH --- VT
  subgraph GATE["drift.py — the weaken gate"]
    CL["classify: strengthen vs weaken<br/><b>vs current effective state</b>"]
    PF["possession factor<br/>TOTP if enrolled, else password"]
    LG2["append-only ledger<br/>_record_change (every attempt)"]
  end
  CH -- "any change" --> CL
  CL -- "strengthen" --> APPLY["apply (frictionless)"]
  CL -- "weaken" --> PF
  PF -- "verified" --> APPLY
  PF -- "refused / no factor" --> DENY["deny, state unchanged"]
  CL --> LG2
  CLI2["cli · dash mode control"] -- "apply_mode_change<br/>(one shared path)" --> CL
  classDef amber fill:#b45309,color:#fff,stroke:#b45309
  classDef block fill:#a83a32,color:#fff,stroke:#a83a32
  class PF amber
  class DENY block
```

Every policy change, strengthen or weaken, approved or denied, is classified against your current state and logged before it takes effect.

See [Tune your policy](https://docs.trydoberman.dev/guides/tuning/) for the day-to-day workflow: reading `doberman tune`'s friction report and accepting a standing elevation for an `AUTH` class you approve every time.
