A policy gate sits between a proposed action and the system that would carry it
out. It answers a bounded question such as "may this refund be issued?" or "may
this change deploy to production?"

The agent can prepare the request. The gate owns the decision. The executor
requires proof of that decision before it acts.

## 1. Name the protected action

Start with one action and one enforcement point. "Check security" is too broad.
"Authorize deployment of artifact digest `D` to production environment `E`" is
specific enough to enforce.

Write down:

- the side effect;
- the target system or resource;
- the identity that performs it;
- the last point where the workflow can still stop it;
- whether the action is reversible;
- the maximum acceptable scope.

Place the gate before the side effect. A check that runs after a message is sent
or money moves is an audit, not a gate.

## 2. Translate prose into a decision table

Take the written policy and remove ambiguous terms. Define the input source for
each condition.

Example policy: refunds above 500 require a person, the order must be settled,
and the refund must not exceed the captured amount.

| Condition                                         | Source                      | Decision         |
| ------------------------------------------------- | --------------------------- | ---------------- |
| Order is not settled                              | Payments system             | Deny             |
| Requested amount exceeds captured amount          | Payments system             | Deny             |
| Requested amount is above 500                     | Request plus policy version | Require approval |
| All required facts are present and no rule blocks | Gate                        | Allow            |
| A required fact is missing or stale               | Gate                        | Indeterminate    |

Use the units and currency defined by the source system. If the source stores
minor units, keep the gate input in minor units rather than introducing a
conversion inside a prompt.

## 3. Define the contract

The gate should receive facts, not a persuasive narrative from the agent.

```json
{
  "action": "refund.create",
  "subject": "order-1842",
  "amount_minor": 42500,
  "currency": "ZAR",
  "captured_amount_minor": 60000,
  "order_state": "settled",
  "facts_observed_at": "2026-08-13T09:30:00Z",
  "request_digest": "sha256:...",
  "policy_version": "refund-v4"
}
```

Return a structured decision:

```json
{
  "decision": "allow",
  "reason_codes": ["ORDER_SETTLED", "WITHIN_CAPTURED_AMOUNT"],
  "policy_version": "refund-v4",
  "request_digest": "sha256:...",
  "expires_at": "2026-08-13T09:35:00Z",
  "decision_id": "gate-7f31"
}
```

Use a closed set of decisions such as `allow`, `deny`, `require_approval`, and
`indeterminate`. Reason codes should also come from a controlled set so callers
can handle them without parsing prose.

## 4. Separate the gate from the proposer

Run the gate with an identity the agent cannot modify or impersonate. The agent
may call the decision interface, but it should not be able to:

- deploy a new policy version;
- edit the facts returned by systems of record;
- mint an allow decision;
- use the executor without a valid decision;
- alter gate logs.

Do not rely on the agent to remember to call the gate. Make the executor reject
requests that lack an applicable decision.

```text
execute(request, decision):
  require decision.decision == "allow"
  require decision.request_digest == hash(request)
  require decision.policy_version in accepted_policy_versions
  require now < decision.expires_at
  require decision has not been consumed
  perform request
```

Binding the decision to the request digest prevents an allow decision for one
request from authorizing a different request.

## 5. Decide how uncertainty behaves

List every dependency the gate needs: policy store, identity provider, source
facts, clock, and decision store. For each dependency, define what happens when
it is unavailable or returns stale data.

Use `indeterminate` when the gate cannot establish the facts needed to allow or
deny. The executor should treat `indeterminate` as a stop. If the workflow can
route the case to a person, make that an explicit transition rather than
converting uncertainty into permission.

Also define:

- how long a decision remains valid;
- whether a decision can be used once or more than once;
- what happens when policy changes after a decision is issued;
- who can grant an exception and how the exception is recorded.

## 6. Make the decision explainable

Store enough information to reconstruct why the gate decided as it did:

```text
decision_id
request_digest
action and subject
policy version
input fact references and observation times
decision
reason codes
expiry and consumption state
```

Keep sensitive values out of general logs. A fact reference and digest may be
enough when the underlying system already holds the value under appropriate
access controls.

## 7. Verify the gate

Test the executor and gate together. A correct policy function is not enough if
the side effect can bypass it.

1. Submit a normal allowed request. Confirm the executor accepts the matching
   unexpired decision.
2. Submit each deny condition separately. Confirm the reason code identifies
   the failed rule.
3. Remove a required fact. Confirm the decision is `indeterminate` and no side
   effect occurs.
4. Reuse an allow decision with a changed amount or target. Confirm the digest
   mismatch blocks execution.
5. Replay a consumed single-use decision. Confirm the executor rejects it.
6. Stop the gate or policy store. Confirm the executor does not fall back to an
   unchecked path.
7. Attempt the side effect with the agent's own credentials. Confirm the target
   system denies it.
8. Change the policy version. Confirm old decisions follow the expiry and
   revocation rules you defined.

## Common failure modes

### The policy is still natural language at runtime

Written policy is useful input to design. It is a poor runtime return type.
Convert each enforceable condition into typed facts, comparisons, and reason
codes. Route genuinely interpretive cases to a named reviewer.

### The gate trusts facts supplied by the agent

An agent can identify a subject, but authoritative amounts, states, and roles
should come from their systems of record. Include provenance and observation
times in the decision input.

### The allow token is not bound to the action

A reusable boolean such as `approved: true` can drift to another target or
amount. Bind the decision to a request digest, policy version, expiry, and use
count.

### There is an emergency bypass with no separate owner

An override controlled by the same agent is part of the normal path. Give
exceptions a separate identity, a narrow scope, an expiry, and a recorded
reason.

### Denials disappear into logs

Return stable reason codes and a next state. The caller should know whether to
correct the request, seek approval, wait for a dependency, or stop.

## Related reading

- [Your agent should not be its own reviewer](/notes/control-loop/your-agent-should-not-be-its-own-reviewer)
- [A passing test is not always a good result](/notes/control-loop/a-passing-test-is-not-a-good-result)
- [Two-plane loop](/reference/two-plane-loop)
- [Closure test](/reference/closure-test)