# Dry-run vs enforce (the two dials) (/docs/concepts/dry-run-vs-enforce)



TokenPolice has **two dials**, and a rule only *acts* — blocks, reroutes — when **both** are
turned to enforce. This is deliberate: it lets you watch what a rule *would* do before you
let it do anything.

## Dial 1 — the SDK `firewall` mode [#dial-1--the-sdk-firewall-mode]

Set once, app-wide, in `tp.init()`. It has three settings:

| `firewall`                        | What happens                                                                                                     |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `"dry_run"&#x60; &#x2A;(default)* | Runs the **full** check on every call and records what it *would* do — but never blocks or reroutes. Watch mode. |
| `"enforce"`                       | Same checks, but now actions actually fire.                                                                      |
| `"off"`                           | No checks at all. Calls are still logged for your dashboard, but no rule can ever act.                           |

```python
tp.init(
    api_key=os.environ["TOKENPOLICE_API_KEY"],
    base_url="https://collect.tokenpolice.ai",
    firewall="dry_run",   # start here; flip to "enforce" when ready
)
```

## Dial 2 — the rule's execution mode [#dial-2--the-rules-execution-mode]

Every firewall rule you create in the dashboard has its own mode: **Dry-run*&#x2A; &#x2A;(the default
for a new rule)* or **Enforce**. This lets you roll out one rule at a time — flip a single
rule to Enforce while the rest keep watching.

## Both must say enforce [#both-must-say-enforce]

An action only fires when the SDK dial **and** that rule's dial both say enforce:

| SDK `firewall` | Rule mode   | Result                                                |
| -------------- | ----------- | ----------------------------------------------------- |
| `dry_run`      | Dry-run     | Logged as *would-block* — nothing happens             |
| `dry_run`      | Enforce     | Logged as *would-block* — nothing happens             |
| `enforce`      | Dry-run     | Logged as *would-block* — nothing happens             |
| **`enforce`**  | **Enforce** | **The action fires** (e.g. `TokenPoliceBlockedError`) |
| `off`          | *(either)*  | No check runs at all                                  |

<Callout type="info">
  In any dry-run combination, TokenPolice records the decision as a *would-block* so you can
  see the impact in the dashboard — it just doesn't touch your call. There's no separate
  "shadow" mode; dry-run *is* the watch mode.
</Callout>

<Callout type="warn">
  **Notify rules have no dial.** A rule whose action is Notify doesn't change your call, so
  there's nothing to hold back: it has no Enforce / Dry-run setting and it sends its alert
  whichever way the SDK dial is set. The two dials govern the actions that touch traffic —
  Block and Reroute.
</Callout>

## The rollout you'll actually do [#the-rollout-youll-actually-do]

<Steps>
  <Step>
    Start with `firewall="dry_run"`. Add your rules. Watch the *would-block* entries pile up in
    the dashboard and confirm they match what you expect.
  </Step>

  <Step>
    Flip the one rule you trust to **Enforce** in the dashboard. Nothing changes yet — the SDK
    dial is still `dry_run`.
  </Step>

  <Step>
    When you're ready to go live, set `firewall="enforce"`. Now every Enforce rule acts; any rule
    still on Dry-run keeps watching.
  </Step>
</Steps>

## Next [#next]

<Cards>
  <Card title="Fail-open" href="/docs/concepts/fail-open" description="Even in enforce, only one designed error ever reaches your code." />

  <Card title="Firewall rules" href="/docs/rules/overview" description="Create the rules these dials switch on." />

  <Card title="See enforce bite (sample app)" href="/docs/sample-apps/budget-aware-rag" description="A RAG app that drives a session past its budget and gets blocked, live." />
</Cards>
