|
| 1 | +--- |
| 2 | +id: spec.policy.compiler |
| 3 | +type: spec |
| 4 | +status: accepted |
| 5 | +chio-node: Policy |
| 6 | +crate: chio-policy |
| 7 | +supersedes: [] |
| 8 | +related-specs: |
| 9 | + - spec.guard.pipeline |
| 10 | +related-receipts: |
| 11 | + - receipt.policy-compile |
| 12 | + - receipt.policy-eval |
| 13 | +related-guards: [] |
| 14 | +graphiti-episode: episode.guard-policy-fail-closed |
| 15 | +related-diagrams: [] |
| 16 | +owners: |
| 17 | + - "@connor" |
| 18 | +last-validated: 2026-05-07 |
| 19 | +--- |
| 20 | + |
| 21 | +# Policy compiler |
| 22 | + |
| 23 | +The deterministic compile-and-evaluate pipeline that turns Chio policy text into the rules each guard runs. |
| 24 | + |
| 25 | +## Normative |
| 26 | + |
| 27 | +> [!normative] |
| 28 | +> Policy text MUST compile to a deterministic evaluator before any policy is enforced. There is no interpretation at exercise time. |
| 29 | +
|
| 30 | +> [!normative] |
| 31 | +> Compilation MUST validate the policy against `spec/schemas/chio-policy/v1/policy.schema.json` before producing an evaluator. Schema-invalid policies fail compilation; **partial compilation is forbidden**. |
| 32 | +
|
| 33 | +> [!normative] |
| 34 | +> Compilation errors MUST be treated as kernel errors (not policy-author errors): the kernel REFUSES to use a policy that didn't compile cleanly. The kernel does NOT fall back to a previous policy automatically — operators MUST explicitly roll back. |
| 35 | +
|
| 36 | +> [!normative] |
| 37 | +> The compiled evaluator MUST be **pure**: no I/O, no clock reads, no random, no network. All inputs to a verdict are explicit arguments. (Replayability follows from purity.) |
| 38 | +
|
| 39 | +> [!normative] |
| 40 | +> Every successful compile MUST emit `receipt.policy-compile.<policy-id>` recording: source content hash, compiled artifact hash, kernel version, and the validation verdict. |
| 41 | +
|
| 42 | +> [!normative] |
| 43 | +> Every policy evaluation MUST emit `receipt.policy-eval.<exercise-id>` recording: matched rule id, verdict, the inputs the verdict depended on (hashed), and the timestamp. |
| 44 | +
|
| 45 | +## Why |
| 46 | + |
| 47 | +> [!guard] |
| 48 | +> Fail-closed extends across the compile boundary. A policy that won't compile is treated like a kernel that won't boot — the system refuses to run rather than degrade silently. |
| 49 | +
|
| 50 | +The decision to make compilation a hard kernel-level failure (rather than a soft "use a fallback policy" graceful degrade) is recorded in [[../episodes/guard-policy-fail-closed]] and reflects three constraints: |
| 51 | + |
| 52 | +1. **Policies are kernel-loaded artifacts.** Treating compile errors as kernel errors keeps the failure mode crisp: the kernel can refuse to boot with a broken policy file, the same way it refuses to boot with a broken config. The alternative ("use the last good policy") creates a covert channel for misbehavior — a kernel could intentionally publish a broken policy to revert to a stale one. |
| 53 | +2. **Evaluator purity is the foundation of audit.** A policy that consults the clock or network for its verdict can't be replayed; "the policy said deny at time X" becomes unfalsifiable. Every input to a verdict must be in the receipt. |
| 54 | +3. **Two receipt classes (compile + eval) keep the audit trail orthogonal.** Compile-time receipts prove **what policy was loaded**. Eval-time receipts prove **how it ruled**. Mixing them would conflate "the policy" with "the policy's decisions," and both audit questions are needed. |
| 55 | + |
| 56 | +## Implements |
| 57 | + |
| 58 | +- `chio_policy::compiler` — `crates/chio-policy/src/compiler.rs` (the compiler entry point) |
| 59 | +- `chio_policy::validate` — `crates/chio-policy/src/validate.rs` (schema validation) |
| 60 | +- `chio_policy::evaluate::engine` — `crates/chio-policy/src/evaluate/engine.rs` (the runtime evaluator) |
| 61 | +- `chio_policy::evaluate::matchers` — `crates/chio-policy/src/evaluate/matchers.rs` (predicate implementations consumed by the guard pipeline) |
| 62 | +- `chio_policy::models` — `crates/chio-policy/src/models.rs` (policy AST types) |
| 63 | +- `chio_policy::lib` — `crates/chio-policy/src/lib.rs` (public API) |
| 64 | +- Schema: `spec/schemas/chio-policy/v1/policy.schema.json` |
| 65 | + |
| 66 | +## Tested by |
| 67 | + |
| 68 | +- `crates/chio-policy/tests/compiler_validate.rs` — schema validation |
| 69 | +- `crates/chio-policy/tests/compiler_purity.rs` — evaluator purity (no I/O, no clock, no random) |
| 70 | +- `crates/chio-policy/tests/evaluator_e2e.rs` — full compile → evaluate flow |
| 71 | +- `tests/conformance/peers/*/test_policies.*` — SDK conformance |
| 72 | + |
| 73 | +When changing the wire format under `spec/schemas/chio-policy/v1/`, prefer the SDK conformance tests over Rust unit tests. |
| 74 | + |
| 75 | +## Operations |
| 76 | + |
| 77 | +- [[../playbooks/release-qualification#gate-3--policy-compiler-validation]] — release-time validation that the candidate's policy schema accepts cleanly. |
| 78 | +- [[guard-pipeline]] — the **consumer** of compiled policies. The relationship is one-way: the policy compiler produces evaluators; the guard pipeline runs them. Compiled artifacts are immutable across guard executions. |
| 79 | + |
| 80 | +## Open questions |
| 81 | + |
| 82 | +- [ ] **Hot reload vs. kernel restart.** Today policy reloads require a kernel restart (so receipt streams cleanly cut over). Hot-reload would reduce latency but complicates the receipt chain — what's the precise compile receipt boundary if the kernel didn't restart? Pending an ADR. |
| 83 | +- [ ] **Policy versioning and rollback.** Operators can roll back to a previous policy by re-pointing the kernel at an earlier compiled artifact. Should the rollback itself be receipted? Likely yes — `receipt.policy-rollback` is a candidate. Open for ADR. |
| 84 | +- [ ] **Cross-issuer policy composition.** Mirror of cross-issuer revocation: if issuer A delegates to issuer B, can B's policies layer on top of A's? Today: no. Pending ADR alongside the cross-issuer revocation question. |
| 85 | + |
| 86 | +## Staleness |
| 87 | + |
| 88 | +> [!warning-staleness] |
| 89 | +> Last validated 2026-05-07. Re-validate against `crates/chio-policy/src/` and the `spec/schemas/chio-policy/v1/` schemas when those crates' major versions bump or when the schema changes. |
| 90 | +
|
| 91 | +The [[../_meta/queries/stale-specs|stale-specs query]] flags this when `last-validated:` is 90+ days behind today. |
| 92 | + |
| 93 | +## Graph context |
| 94 | + |
| 95 | +%% kb_neighbors: spec.policy.compiler depth=2 %% |
| 96 | + |
| 97 | +## Lineage |
| 98 | + |
| 99 | +Derived from the seed [[../episodes/guard-policy-fail-closed]] (migrated from arc PR #599's `seeds/graphiti/guard-policy-fail-closed.json` via `make kb-migrate-seeds`). The seed is shared with [[guard-pipeline]] — both specs are Chio-side surfaces of the same architectural commitment to fail-closed everywhere. |
0 commit comments