# Capability Policy
**Status**: living document; schema versioned by the `CapPolicy` TOML surface.
A capability policy is a TOML document loaded via `enprot --policy-file
<policy.toml>`. It has three sections:
| `[chain]` | Trust roots, timestamp monotonicity | `verify-chain` |
| `[[word]]` | Legacy per-WORD capability requirements | `encrypt`, `enprot cap` |
| `[[rule]]` | Declarative rules (condition → action) | `encrypt`, `enprot cap` |
The policy is **fail fast**: capability names are validated when the
document loads. An invalid name aborts the run at `--policy-file` load,
not at the first encrypt that touches the WORD.
## `[chain]` — chain verification axis
```toml
[chain]
# When non-empty, verify-chain rejects any anchor whose signer is
# not in this list. Empty means "no whitelist".
trust_roots = [
"ed25519:9f3a7b0000000000000000000000000000000000000000000000000000000000",
]
# When true, anchor timestamps must strictly increase along parent edges.
require_monotonic_timestamps = true
```
## `[[word]]` — legacy per-WORD requirements
```toml
[[word]]
name = "Agent_007"
required_capability = "viewer" # viewer | reader | signer | verifier
accepted_recipients = [ # ML-KEM recipient whitelist (introspection-only today)
"ml-kem:1c8d2e0000000000000000000000000000000000000000000000000000000000",
]
```
`decryptor` is not valid as a `required_capability` — decryption
rights are inherently per-WORD and expressed by the caller's key
material, not by a policy tier. A document using it fails to load.
## `[[rule]]` — the rule engine
Each rule is a `(condition, action)` pair. Rules evaluate in document
order; **the first matching rule decides**; when no rule matches, the
decision is *allow* (open default). Add an `always → deny` catch-all
for a default-deny posture.
```toml
# Require a capability for one WORD.
[[rule]]
condition = { kind = "word", word = "TOP_SECRET" }
action = { kind = "require", capability = "verifier" }
# Unconditionally allow a public WORD.
[[rule]]
condition = { kind = "word", word = "PUBLIC" }
action = { kind = "allow" }
# Default-deny everything else.
[[rule]]
condition = { kind = "always" }
action = { kind = "deny", reason = "no matching rule" }
```
### Conditions
| `word` | `word` | the requested WORD is exactly `word` |
| `always` | — | every request (catch-all) |
### Actions
| `allow` | — | allow the request |
| `require` | `capability` | allow iff the caller holds `capability` (`viewer`, `reader`, `signer`, `verifier`) |
| `deny` | `reason` | deny with `reason` surfaced in the error |
### Evaluation order with `[[word]]` entries
`[[word]]` entries compile into rules **before** `[[rule]]` entries,
so a `[[word]]` requirement decides before any `[[rule]]` catch-all.
Within each section, document order applies.
## Semantics summary
- First match wins, per section order above.
- No match → allow.
- A `require` failure reports `WORD <w> requires <capability>; caller
does not hold it` as a `PolicyViolation`.
- Unknown fields anywhere in the document are rejected
(`deny_unknown_fields`) — typos fail at load, never silently pass.
## Extending the engine
The evaluation loop is closed for modification. A new rule kind is:
1. a new `Condition` (or `Action`) variant plus its match arm, and
2. its serde counterpart in the wire enums.
The loop itself never changes. See `src/cappolicy.rs`.