> For the complete documentation index, see [llms.txt](https://govplane.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://govplane.gitbook.io/docs/documentation/basic-concepts/policies-and-rules.md).

# Policies and Rules

A **policy** is a named group of rules about one area of your application — logins, refunds, exports. A **rule** is one decision inside it: what it covers, when it applies, and what it produces.

Policies live in a bundle. Your application never writes them; it reads a bundle and asks it questions.

### Shape

```ts
interface RuntimePolicy {
  policyKey: string;        // unique across the bundle
  activeVersion: number;
  defaults?: PolicyDefault; // what applies when no rule fires
  rules: RuntimeRule[];
}

interface RuntimeRule {
  id: string;               // unique within its policy
  status: "active" | "disabled";
  priority: number;         // higher wins, within one effect type
  target: { service: string; resource: string; action: string };

  when?:       ConditionAST; // optional — see Conditional Rules
  thenEffect?: Effect;       // applied when `when` is true  (falls back to `effect`)
  elseEffect?: Effect;       // applied when `when` is false (rule is skipped if absent)
  effect:      Effect;       // unconditional / fallback effect

  description?: string;
}
```

A complete policy:

```json
{
  "policyKey": "login-protection",
  "activeVersion": 1,
  "defaults": { "effect": "allow" },
  "rules": [
    {
      "id": "r_deny_after_five",
      "status": "active",
      "priority": 100,
      "target": { "service": "auth", "resource": "login", "action": "authenticate" },
      "when": { "op": "gte", "path": "failedAttempts", "value": 5 },
      "effect": { "type": "deny" }
    }
  ]
}
```

### Targets

A target is the triple a rule covers:

```json
{ "service": "auth", "resource": "login", "action": "authenticate" }
```

Matching is **exact on all three**. There is no wildcard matching and no hierarchy — `resource: "reports"` does not cover `reports/monthly`. If a rule should cover several shapes, write several rules.

The triple is a convention, not a schema. Pick one and hold to it across the bundle:

| Part       | Meaning                | Examples                          |
| ---------- | ---------------------- | --------------------------------- |
| `service`  | The subsystem          | `auth`, `payments`, `api`         |
| `resource` | What is being acted on | `login`, `refund`, `report`       |
| `action`   | What is being done     | `authenticate`, `execute`, `read` |

### Priority

`priority` orders rules **within a single effect type**, higher first. It does not promote a rule past a stronger effect.

```
kill_switch  >  deny  >  throttle  >  allow  >  custom
```

A `kill_switch` at priority 1 beats a `deny` at priority 100. Precedence is decided first, priority only breaks ties inside the winning group. See Effects.

When two rules of the same effect type share a priority, the engine falls back to `policyKey` then `ruleId`, alphabetically — so the outcome is stable rather than dependent on array order.

### Status

Only `status: "active"` rules can fire. Anything else — `"disabled"`, or a missing status — is skipped.

{% hint style="info" %}
A rule with no `status` never fires. `govplane build` always writes one, but a hand-edited bundle may not have it.
{% endhint %}

Disabling a rule does **not** remove its policy from consideration. The policy still covers that target, so its default still applies — see below.

### Policy defaults

`defaults` is what the policy says when **none of its rules fire** — a rule aimed at the target whose `when` turned out false, or one that is disabled.

```json
{ "defaults": { "effect": "allow" } }
```

Every effect type is available, with its descriptor where one is needed:

```json
{ "defaults": { "effect": "deny" } }
{ "defaults": { "effect": "throttle", "throttle": { "limit": 100, "windowSeconds": 60, "key": "tenant" } } }
{ "defaults": { "effect": "kill_switch", "killSwitch": { "service": "payments" } } }
{ "defaults": { "effect": "custom", "customEffect": "{\"tier\":\"free\"}" } }
```

A default decision reports `reason: "default"` and carries no `ruleId`:

```ts
{ decision: "allow", reason: "default", policyKey: "login-protection" }
```

#### Defaults only speak for what the policy covers

**A policy's default applies only to targets that policy governs** — the targets its own rules name.

This matters more than it first appears. Consider a bundle with two unrelated policies:

```json
[
  { "policyKey": "login-protection", "defaults": { "effect": "allow" },
    "rules": [ { "target": { "service": "auth", "resource": "login", "action": "authenticate" }, "…": "…" } ] },

  { "policyKey": "refund-control", "defaults": { "effect": "deny" },
    "rules": [ { "target": { "service": "payments", "resource": "refund", "action": "execute" }, "…": "…" } ] }
]
```

A login request is evaluated. `refund-control` has nothing aimed at logins, so it stays silent and the answer comes from `login-protection`. Were it consulted, its `deny` default would outrank the allow — because deny beats allow — and **one refund policy would deny every request in the bundle**.

Two consequences worth knowing:

* **A policy with no rules governs nothing.** It has declared no scope, so its default never applies anywhere. It is inert until you give it a rule.
* **Scope is read from the rule's target alone**, before status and conditions. A disabled rule, or one whose `when` is false, still keeps its policy in scope. Switching a rule off therefore cannot silently swap the policy's own default for the global deny-by-default.

Targets that **no** policy governs still fall to deny-by-default, so silence never means permission.

### Rule identity

`ruleId` is unique **within a policy**, not across the bundle. Two policies may each contain a rule called `deny-large`, and such a bundle is valid.

That only matters if you address a rule directly — see Evaluation. For ordinary target-based evaluation it never arises.

{% hint style="warning" %}
If you use rule-ID-based evaluation, keep rule IDs unique across the whole bundle, or always pass `policyKey` alongside. Otherwise the first match in bundle order wins, which may not be the rule you meant.
{% endhint %}

### Where to draw the line between policies

One policy per area of behaviour, named for that area. `login-protection`, `refund-control`, `export-limits`.

Two rules of thumb:

* **Rules that share a default belong together.** The default is a policy-level statement, so rules that disagree about what "nothing fired" should mean belong in different policies.
* **Rules that cover the same targets belong together.** Splitting them means two policies both govern the target, and both defaults come into play.

### Next

* [Effects](/docs/documentation/basic-concepts/effects.md) — what a rule can produce, and their precedence
* [Conditional Rules](/docs/documentation/basic-concepts/conditional-rules.md) — the `when` clause
* [Evaluation](/docs/documentation/basic-concepts/evaluation.md) — how a decision is reached
* [Bundles](/docs/documentation/basic-concepts/bundles.md) — how policies are packaged and verified


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://govplane.gitbook.io/docs/documentation/basic-concepts/policies-and-rules.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
