> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.prividium.com/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

Policy is a list of rules over privileged operations. **Every rule is evaluated** and the **severest verdict decides**:
a block beats a request for approval, which beats an allow. An operation no rule matched is decided by the **default
rule**, the last row of every rule list. It blocks, and it cannot be removed or duplicated — only switched off.

Position carries no meaning. A rule says what it says wherever it sits, so a guardrail cannot be defeated by a
permissive rule above it, and reordering the list changes nothing.

> **Rules written when the first match decided are rewritten on upgrade.** A spending limit, or a maximum on a rule that
> permits, becomes a rule of its own that refuses past it, and every allowance keeps what it had spent. The rewrite never
> refuses less than the rules did. Where a later rule used to take over past a cap — *allow up to the cap, ask for
> approval above it* — both rules now apply to everything they match, so write the approval rule with the limit instead.

**What a policy covers depends on whether organizations are enabled.** With them off there is one policy and it governs
the whole zone — every user, since every user is a zone user. With them on, policy is an organization's: each publishes
its own, and a signer belonging to no organization is governed by none. The zone's own policy is unavailable in that
configuration, so one left behind by enabling organizations stops governing anything rather than quietly refusing
traffic. Switching back makes it govern again, unchanged.

Policy sits **on top of** contract permissions, it does not replace them. Contract permissions decide whether an
identity may touch a method at all; policy then decides whether this particular operation may proceed, and under what
limits or approvals. Both have to allow it. See [Permissions](/configuring/contracts/permissions) for the layer
underneath.

Governance is a feature the Prividium API registers, not a separate deployment. Enable it with
`POLICY_RULES_ENABLED=true` on the API. The admin panel needs no flag of its own — it reads the registered features from
`GET /info` and shows the Policy section only when the API actually serves it.

Off is a real off switch rather than a permission change: the routes are never registered, so they answer 404. Turning
it back on changes nothing about the data — the `policy_*` tables and their migrations are left alone either way.

> Do not confuse this with `POLICY_PORT`, which mounts the sequencer's validator listener. That listener is where
> governance is *enforced* at the chain boundary; the routes here are where it is *authored*. With
> `POLICY_RULES_ENABLED` off, the listener still serves `/admit` — it just consults no governance overlay.

## What a rule matches on

Every condition is optional, and leaving one out means "any". A rule that constrains nothing matches every operation of
its type, which the rule list flags.

| Condition      | Matches                                                                                             |
| -------------- | --------------------------------------------------------------------------------------------------- |
| Operation      | Exactly one of: deployment, contract call, transfer, token approval, mint, burn, raw, typed message |
| Initiator      | Selected users, machine identities, or holders of a role                                            |
| Source         | Selected wallets, or the wallets of a role's holders                                                |
| Destination    | Selected addresses or contracts, **or what the destination is** (see below)                         |
| Method         | Selected 4-byte selectors                                                                           |
| Primary type   | Selected EIP-712 primary types (typed messages only)                                                |
| Parameters     | A decoded argument in a set of addresses, or within a numeric range                                 |
| Asset          | The native token, or selected token contracts                                                       |
| Amount         | A minimum and maximum for a single operation; for a call or deployment, the native value it sends   |
| Spending limit | Holds once a rolling window's allowance is spent                                                    |

The action is **Allow**, **Block**, or **Require approval**.

> **A published policy governs everything, not only what it mentions.** Any submitted operation no rule matches is
> blocked by the default rule — inner calls are the exception, and are covered under
> [Where policy is enforced](#where-policy-is-enforced). So a ruleset that only names a token blocks contract
> deployments and plain native transfers for that organization until you add allow rules for them. The same is true of a
> ruleset whose every rule is disabled: the default rule blocks all of them.

### The default rule

It sits pinned at the bottom of the rule list, and it is the rule that decides most traffic. Switching it off makes the
opposite posture explicit: an operation no rule above matched is **allowed through** to the permission layer rather than
blocked.

That is a real choice and it is treated as one. It changes the draft's digest, so a reviewer who approved a digest
cannot find the deny switched off underneath it; it shows in the draft comparison as a change to the last rule, in the
sentence the reviewer reads before approving; and the policy editor carries a warning for as long as the draft has it
off. Reaching production still takes the same dual control as any other publication.

Switching it off is not how you turn governance off. Unset `POLICY_RULES_ENABLED` on the API for that: with the default
rule off, rules that block or require approval still bite.

**A spent limit is still a limit.** A limit is a rule of its own, so once its allowance is spent it refuses with
`policy.limit_spent` whichever way the default rule is set — unless a rule no window frees refuses the same operation,
which is then the reason given. Turning the default rule off lets through what your rules never addressed; it does not
lift the limits they set.

### Destination classification

A rule can match on **what the destination is**, rather than naming it:

| Class      | Meaning                                                         |
| ---------- | --------------------------------------------------------------- |
| `contract` | A contract registered to this organization, or a zone-level one |
| `wallet`   | An address associated to some user                              |
| `unknown`  | Neither — the one-time-address case                             |

There is no allowlist to maintain: this is derived from the contract registry and wallet associations the deployment
already holds. So *"block any transfer to an unrecognized address"* is one rule with `classification: [unknown]`.

Named addresses and classifications are **alternatives** within the condition, the same as every other dimension: a rule
listing both matches an operation that satisfies either.

Classification is deliberately **not** part of an operation's identity. Registering a contract, or associating a wallet,
changes how a future operation classifies but never changes the digest of one already decided — so an approval granted
earlier cannot be stranded by an unrelated registry change.

### Signing requests are checked before signing only

`raw` and `typed_message` are signing requests, not transactions. They never reach the chain, so a rule over them binds
whoever asks **before** signing and has **no chain-boundary backstop** — unlike every transaction type, which is checked
again as it enters the chain. Treat them as a control on your own initiating service, not as an enforcement guarantee.

Because so little is knowable about them, most conditions do not apply:

* `raw` — initiator and source only.
* `typed_message` — initiator, source, primary type, and the domain's verifying contract (matched as the destination).

A rule that sets an inapplicable condition is a validation **error**, not a silent never-match.

### Amounts are stored in base units

There is no currency conversion anywhere in the engine. A limit of `15000000000` on a 6-decimal stablecoin means 15,000
tokens. Nothing is denominated in USD or EUR.

The admin panel reads a token's decimals from the chain, so a rule that names exactly one asset takes and shows its
amounts in that asset's own unit — `15000` for the stablecoin above — with the base units beside them. A rule that
matches several assets, or any, has no single scale, so its amounts are entered and shown in base units, and so are those
of a token whose decimals cannot be read. A plain call or a deployment moves native value, so its amounts are in the
chain's own token.

### Amount bounds apply per call and per transaction

An amount on a rule says **when that rule applies**, not what a transaction may move. A rule permitting transfers of at
most 15,000 does not refuse anything larger — it simply does not apply to it. To refuse above an amount, write the rule
that refuses:

```
block transfers of 15,001 or more
```

That bound is checked twice: against each call, and against everything the transaction moves under the same rule. Without the second check a transaction could move any multiple of the bound by splitting the movement
across the inner calls it makes.

The total adds up the calls that stay under the bound while the rule's other conditions hold, its spending limit
included. A call that reaches the bound is the rule's own to decide: a block refuses it, and an approval rule needs
that call's approval, which then covers it. What a registered contract pays out of its own balance adds nothing; see
[what an inner call counts](#where-policy-is-enforced) below.

Nothing is netted. What comes back is not a movement *from* the governed address, so it never counts and never reduces
the total — moving an amount out and back inside one transaction does not reset what has been counted.

Refusing transfers of 15,001 or more:

| The transaction          | Result                                                          |
| ------------------------ | --------------------------------------------------------------- |
| one transfer of 15,000   | allowed                                                         |
| five transfers of 15,000 | refused — 75,000 in total                                       |
| one transfer of 20,000   | refused — over the bound on its own                             |
| out 15,000, back 15,000  | allowed; a second round trip in the same transaction is refused |

### Operation type is derived, never declared

The engine works out the operation type, asset, amount, and decoded arguments from the transaction itself. A caller
cannot label its own operation to dodge a rule. For calldata with a method selector, a missing contract ABI or
undecodable arguments causes **refusal**.

Nonempty calldata shorter than four bytes is classified as a contract call. It can execute a contract's fallback, so
`call` rules govern it even without a method selector, including under an Umbrella permission. Native transfers with empty
calldata remain `transfer` operations.

Policy applies the [argument decoding limits](/configuring/contracts/permissions#argument-decoding-limits) to each
governed contract call, even when no rule constrains its parameters. These limits cover all arguments, including arrays,
tuples, and dynamic values that policy does not retain as matching facts. Only top-level addresses and unsigned integers
become parameter facts.

Malformed or over-budget arguments return HTTP 422 from policy evaluation and are refused at `/admit` and `/judge`
with `policy.frame_unclassifiable`. A contract permission granting access does not bypass this policy check.

Only the standard transfer, approve, mint and burn signatures carry a token amount; any other method is a contract call.
A contract call's amount is the native value it sends, so its request in **Policy → Approvals** also shows the call
itself: the contract, the method with its selector, and each argument by name (amounts in base units). The names are
decoded with the ABI registered for the contract in this organization, as it is registered now; other calls list the
addresses and numbers only.

### Spending limits: pooled or per-value

A spending limit is a **condition**, not a ceiling on the rule that carries it. It holds once this operation would take
the window's allowance past its bound, so the rule's action is what happens **above** the allowance:

```
USDC transfers                            →  allow
USDC, daily spend over 10,000 per user    →  require approval
USDC, daily spend over 100,000 per user   →  block
```

Each line reads on its own, and two limits over the same traffic both bind — neither owns the other's counter. A limit
goes on a rule that blocks or asks for approval: on one that permits, it would permit only what goes over the
allowance, so the editor does not offer it there and the API refuses it.

A limit needs a window and a scope:

* **One shared limit** — every matching operation draws on the same allowance.
* **Per source / initiator / destination** — each distinct value gets its own allowance of the same size.

Concurrent requests cannot together exceed a limit; the check and the record are taken under a lock.

A charge sits in the window from when its spend takes place: when the transaction runs, and until then from when the
decision became usable — the moment it was approved, for one that needed approval. A quorum that takes longer to form
than the window lasts still charges the window it is spent in.

### Filing rules into groups

A rule can carry a **group**, which is a folder in the rule list and nothing more — the engine never reads it, so
moving a rule between groups cannot change a decision. Groups are flat: one level, no nesting.

A group exists exactly as long as a rule names it. Editing a rule offers the groups already in use, so a second rule
joins one rather than founding a near-duplicate, and a new name creates the group as the rule is saved. Rules in no
group collect under **Ungrouped**, last.

From the group's menu you can rename it — which moves every rule filed under it and changes nothing else — take the
rules out of the group, or delete the group **and the rules in it**. That last one asks first and names how many rules
go with it. Like every edit, none of it governs anything until the draft is published.

### Seeing what a limit holds

**Policy → Limits** lists every enabled rule that carries a limit over time, with what it has consumed, what remains,
and when the window frees. A pooled limit reads as one row; a rule scoped by person, source or destination reads as one
row per distinct value, ordered by how much each has consumed. The window frees as its oldest charge ages out, not all
at once, so "frees" is a moment rather than a reset time.

Search by wallet address or by person to answer the question an operator actually arrives with — "why was this
refused?". An allowance nobody has drawn on has no row of its own, so a search for a wallet or a person adds one reading
zero rather than coming back empty. This applies to a rule scoped by a single source or person; a destination or
multi-key rule adds none.

The figures are computed on each read by the same query the engine charges against, so they are the numbers an
evaluation at that instant would have used — two reads seconds apart can legitimately differ, and a decision stops
counting the moment it can no longer reach the chain. Nothing is cached; **Refresh** re-reads on demand.

A cap on a single operation is a different thing and is not shown here: it holds nothing and frees nothing. It is part
of the rule, on the **Rules** tab.

The tab only reads. Resetting or adjusting a consumed allowance is deliberately not offered there.

### Which decisions consumed it

**Policy → Decisions** lists the recorded decisions, newest first, filterable by rule and outcome and searchable by a
wallet address on either side of the operation or by a decision id. Each row opens the rule-by-rule trace behind that
decision.

A decision is recorded when an operation is evaluated **before** submission. A rule that consumes state — a limit over
time, or an approval — cannot be admitted any other way, so everything charged against such a rule appears here. An
operation decided at the chain boundary against rules that consume nothing leaves no record, because nothing was
reserved.

## Drafts and publication

Editing never touches the live policy. You work on a single draft per policy — one per organization, or one for the
zone where organizations are off — then publish it as a new immutable version.

1. **Edit the draft.** Add, edit, disable, or remove rules.
2. **Read the validation report.** Errors block publication; warnings are for the reviewer to weigh.
3. **Publish.** Publication is an approval-gated request, not an immediate write. **Approve** first shows what changes
   against the active policy and the rules that result; approving there opens a request and records your own approval
   on it, and the ruleset takes effect the moment the request reaches its threshold. The published version records its
   author, its reviewer, and an integrity digest.

By default the threshold is **one approval from an administrator who is not the draft's author**, so a reviewer's
approval publishes and nothing waits — the same act it was before publication became governed. Raise the threshold and
the request waits in **Policy → Approvals** until enough approvers act; approving there opens the same review.

The draft's author may ask for publication, but their own approval is never counted. That is dual control: the author
can put a change forward, and somebody else decides whether it takes effect.

Publishing supersedes the previous version, which stays readable in the version history. To roll back, load an earlier
version into the draft and publish it as a new version — history is never rewritten.

### How many approvals publication needs

**Policy → Approvals** (the button on the policy page) sets this for whichever policy you are editing — an
organization's, or the zone's:

| Setting               | Default                          | Meaning                                                                      |
| --------------------- | -------------------------------- | ---------------------------------------------------------------------------- |
| Approvals required    | 1                                | How many eligible approvals a draft needs before it takes effect             |
| Approver role         | The administrators of that scope | Who may approve. Naming a role limits approval to that role's holders        |
| Requests expire after | 168 hours                        | An expired request publishes nothing; the change has to be put forward again |

Left as administrators, zone operators may approve as well, which matches who can publish. A threshold higher than the
number of eligible approvers is refused when you save it, rather than at the point someone is trying to ship.

**Weakening these settings waits a day.** Strengthening the gate — more approvals, or a narrower set of approvers —
applies the moment you save it. Weakening it is accepted but held: the settings in force keep governing for 24 hours,
and the dialog shows what is waiting and from when. Saving a setting that is no weaker before then replaces the waiting
change and takes effect at once.

Fewer approvals, or an approver who is not eligible today, both count as weakening — repointing the approver role at one
you can hand to a colleague of your choosing loosens the gate exactly as lowering the count does. Changing only the
expiry never waits, because a request freezes its own expiry when it opens.

The delay is there because `admin_write` on its own reaches these settings. Without it, one administrator could drop the
threshold and repoint the approver role in the moment before publishing, and so authorize their own policy change. The
change is audited when it is accepted, under its own action, so the day it spends waiting is a day it can be seen and
reverted in.

A publication request behaves like any other approval request: its approver set is frozen when it opens, a single
rejection settles it, and it is bound to the exact rules that were reviewed. Editing the draft while a request is open
invalidates that request, because the approvals it holds were given to content the draft no longer has. Discarding the
draft cancels the request but keeps it on record, digest included, so an approval that was given stays visible.

### Validation

| Finding                                           | Severity | Meaning                                                                   |
| ------------------------------------------------- | -------- | ------------------------------------------------------------------------- |
| Unknown role / user / machine identity / contract | Error    | The rule points at something outside this organization                    |
| Spending limit on an amountless operation         | Error    | A signing request carries no amount, so the limit cannot apply            |
| Approval threshold above the role's membership    | Error    | The quorum could never be met                                             |
| Inapplicable condition                            | Error    | The operation cannot supply that condition, so the rule could never match |
| Unconstrained rule                                | Warning  | The rule matches every operation of its type                              |
| Asset that is not a registered contract           | Warning  | Governing an unregistered token is allowed, so this flags a likely typo   |

## Version history and offline copies

**Policy → Version history** lists every version with its status, rule count, author, reviewer, publication time, and
integrity digest. Selecting one shows the rules exactly as they were published.

Two things you can do from there:

* **Export** a version as JSON — the rules, the digest, the schema fingerprint, and who published it. This is the
  offline copy an auditor keeps; the digest is what they check a claim against.
* **Load into draft** — the rollback path. It copies that version's rules into the draft for review; publishing then
  creates a *new* version. Earlier versions are never rewritten or resurrected in place — except once, by the upgrade
  that rewrote limits and maximums as rules of their own. It recomputed each version's digest, and kept the one the
  version was published under in its note.

An author or reviewer who is a member of the organization is shown by name. A zone operator is not a member, so their
identifier is shown instead; `audit_logs` carries the full actor either way.

## Approvals

A **Require approval** rule opens a request instead of deciding immediately. An approval group is an **existing role
plus a threshold** — there is no separate group entity to maintain.

* The eligible approvers are frozen when the request opens, so nobody given the role later can act on it. Taking the
  role away works the other way: that approver can no longer vote, and their earlier approval stops counting toward any
  request still open.
* A single rejection settles the request; it is not out-voted by later approvals.
* One approver counts once, however many times they act. A rule that needs every one of its groups needs a different
  approver for each, so someone holding two of the roles fills only one of them.
* The request is bound to the exact operation. Change any field and you get a different request; the original approval
  cannot be reused for it.
* Publishing a new policy version invalidates open requests raised under the old one.
* Whether the initiator may approve their own operation, and whether machine identities may approve, are per-rule
  choices.
* An operation several approval rules match needs every one of them. Its request carries each rule's groups, is
  approved once all of them are met, and expires with the shortest of their windows. A restriction any of them sets —
  the initiator may not approve, only people may approve — holds for the whole request.
* Publication requests share this queue. They gate a ruleset rather than an operation, so they carry no source,
  destination, or amount, and only identities that may approve one can see it.

## Where policy is enforced

Two places, and they agree by design:

Neither place governs a read. A simulated `eth_call` and gas estimation move nothing, so they are left to the permission
layer — otherwise preparing a transaction would be refused by the very rule the caller is about to ask a decision for.

**Before signing.** The initiator submits the operation and gets back allow, block, or approval-required, along with a
decision id. This is where an approval request is opened and where spending allowance is consumed.

**At the chain boundary.** Every transaction is checked again as it enters the chain. If a decision was already obtained
for that exact operation, it is honoured; otherwise the ruleset is applied on the spot.

An operation matching a rule that consumes state — a spending limit or an approval — **cannot be admitted without
going through the pre-submission step first**, because nothing at the chain boundary can reserve allowance or wait for
an approver. Such a transaction is refused with `policy.decision_required`.

**Inner calls.** The chain boundary also checks the calls a contract makes on the signer's behalf, and every kind of
rule reaches them — a limit or an approval included. An inner call governed by a rule that consumes state draws on **a
decision of its own**, recorded for that call rather than for the operation that was submitted. That is what makes an
asset-scoped limit usable on a routed transfer: "cap USDC per day" does not match a call to a router, which carries no
asset, but it does match the transfer the router makes out of the signer's wallet.

**What an inner call counts.** An inner call charges a spending limit or adds to a transaction total with the tokens it
draws from any wallet — the signer's, as a router's `transferFrom` does, or another member's that approved the same
contract — and with native value it sends anywhere but back to one of the signer's wallets. A registered contract paying
out of its own balance — a pool's payout, a vault's withdrawal — is not counted again on top of the pull that funded it,
and needs no decision for a limit. It still has to clear every rule that blocks or asks for an approval.

This holds whether or not the pull happened in this transaction: value a registered contract already holds for the
signer, such as a vault position, is outside every limit and total, and only blocks and approvals govern where it goes.
Anything else counts — a contract that is not registered, a delegatecall, a mint, or a call whose calldata carries more
than its arguments, as one made through an ERC-2771 forwarder does.

An inner call with no decision to draw on is refused with `policy.decision_required`. The refusal names the kind of
operation and where it went, and stops there: the calldata and the amount are computed from state the submitter may hold
no disclosure on. Obtaining a decision needs the exact call, which today only whoever wrote the contract interaction has
— an initiator who did not write it cannot complete the loop from the refusal alone. Where the rule's window is already
spent it is refused with `policy.limit_spent` instead: no decision can be obtained for it until the window frees, so
asking for one would be an instruction nobody can follow. The same call made twice in one transaction needs two
decisions: a batch of equal payouts is one governed operation several times over, and one decision covering all of them
would charge the limit once for the whole batch.

An inner call no rule matches is left to the capability gate rather than blocked. Asking about such a call and getting a
default-deny answer does not change that: a refusal is never a decision to draw on. A call that draws on a spending
limit is not that case, whether or not a rule permits it. It needs a decision of its own, or its spend would never
reach the window, and where the default rule is on it is refused as the same operation submitted would be.

Nothing is spent unless the whole transaction is admitted: a trace one inner call refuses consumes no allowance from the
others.

Where organizations are enabled, signers belonging to none are not governed by any policy.

### How long a decision stays good

A decision is tied to the ruleset version that produced it, and it expires.

* **Publishing supersedes it.** A decision granted by an earlier version stops being honoured the moment a new version
  is published, so a rule you add to block something takes effect even for operations already evaluated under the old
  policy. The boundary simply re-evaluates.
* **It ages out after 5 minutes.** Measured from whichever is later, when the decision was recorded or when its approval
  was granted. Past that the decision is no longer honoured and the operation must be submitted again.
* **One transaction holds it, and executing confirms it.** The first transaction to reach the chain boundary with that
  decision reserves it, and the boundary keeps admitting that same transaction — it is asked more than once per
  transaction. A second transaction carrying the same operation is refused with `policy.decision_required` and has to be
  decided in its own right, so one approval moves one transaction and a spending limit is charged once per transaction
  rather than once per operation. The decision is spent for good once the transaction clears every check at execution,
  its inner calls included. The boundary does not see whether it then reverts, so a reverted transaction has spent it
  too. Reaching execution holds it 5 minutes more, and a transaction whose hold lapsed before it was judged is refused
  with `policy.decision_required`: its allowance may already back another decision.
* **A transaction that never executes gives the allowance back.** A transaction that is replaced, dropped, refused at
  execution or simply never sent holds its decision until the decision ages out, and then stops counting: the allowance
  is free again. A replacement is a second transaction, so it needs a decision of its own.
* **An inner call's decision is taken when the transaction runs.** At submission the calls a transaction will make are
  only a forecast, so their decisions are checked but not reserved.
* **Simulating spends nothing.** Gas estimation runs the transaction through the same checks and gets the same answer,
  but it neither holds nor confirms, so it cannot consume what the transaction it is preparing for still needs.

Do not confuse that window with a rule's approval expiry, capped at 7 days: that is how long an approval *request* may
stay open awaiting approvers, not how long the resulting decision is honoured.

> **Guard the policy listener's bearer token like a chain secret.** Anyone who can reach the listener with it can spend
> a governed signer's decision against a transaction hash of their choosing, which leaves that signer's real transaction
> refused until they ask for a decision again. Keep the listener on the network the sequencer uses and nothing wider.

> **Policy needs a sequencer that names the transaction and the moment on both `/admit` and `/judge`.** One that
> predates them never confirms a hold, so an executed spend stops counting once its decision ages out, and an ask
> without a hash can ride a decision another transaction already holds.

**An approval under another rule does not take a call out of the total.** Two inner calls each approved on their own
still add up against a rule refusing at a bound, and the transaction is refused once the total reaches it. Split it, or
set the bound to what one transaction may move in aggregate.

### Decision ids on a refusal

| Rule id                              | Meaning                                                                               |
| ------------------------------------ | ------------------------------------------------------------------------------------- |
| `policy.rule.block`                  | A rule blocked it                                                                     |
| `policy.default_deny`                | No rule matched, so the default rule blocked it                                       |
| `policy.default_allow`               | No rule matched and the default rule is switched off, so it was let through           |
| `policy.limit_spent`                 | A rule would have permitted it, but its limit for the window is spent                 |
| `policy.decision_required`           | Needs a pre-submission decision that was never obtained                               |
| `policy.approval_missing`            | A decision exists, but its approval is not granted                                    |
| `policy.frame_unclassifiable`        | An inner call frame could not be decoded, so it was refused rather than guessed       |
| `overlay.unavailable`                | Governance is enabled but could not be reached; the boundary fails closed             |
| `policy.trace_too_large`             | The transaction has more governed inner calls than policy will walk                   |
| `policy.transaction_amount_exceeded` | The calls under one rule add up to the amount it refuses at, though each was under it |

## Machine access

An M2M application needs the `org_policy_access` system permission to use the policy engine, in addition to being linked
to the organization. Being linked alone is not enough — see
[System permissions](/configuring/users/system-permissions#m2m-organization-policy-access).

With organizations switched off there is no organization to be linked to: the application needs the same permission held
zone-wide, through a role that names no organization. A grant inherited from an organization does not reach the zone's
policy.

An application can evaluate and simulate operations. It cannot submit one that matches a spending limit or an approval
rule: the chain boundary only honours decisions recorded for the signing wallet's user, so such an operation from a
machine identity is refused with `policy.decision_required`. For the same reason, a decision an application records
reserves no allowance.

## The inspector

The inspector answers "what would happen to this operation, and why" without recording anything or consuming allowance.
It shows the normalized operation, its digest, every rule visited, and which condition ruled each one out —
against either the active policy or the draft.

Use it to check a draft before publishing it.

### Explaining a decision that already happened

To explain something that was actually decided rather than a hypothetical, open the decision itself. It shows the same
rule-by-rule trace, but for the recorded outcome.

* From **Approvals**, use **Why?** on any row.
* By link: `/policy?decision=<decisionId>` opens the trace directly, so a refusal can be explained from an audit entry
  or a chat message without reconstructing the operation by hand.

## Trying it locally

Publication needs two administrators, because the author's own approval is never counted. The local stack seeds only one
(`admin@local.dev`); every other seeded user holds the `user` realm role, and approvers are drawn from the holders of a
role carrying `admin_write`. So grant a second user an administrator role before starting, or publication will open a
request that nobody present can approve.

1. Give a second seeded user (say `test@local.dev`) an administrator role — the `admin` realm role in Keycloak, or an
   organization role carrying `admin_write` from **Roles** in the admin panel.
2. Sign in as `admin@local.dev`. With organizations enabled, pick one in the organization switcher — policy is that
   organization's, so nothing is shown until one is selected. With them switched off, **Policy** opens directly on the
   zone's own.
3. Under **Policy**, open the draft, add a rule, and save it, then request publication.
4. Sign in as the second administrator and approve the draft. Requesting it as `admin@local.dev` records no approval of
   their own — that is dual control working.
