ferrin-policy
Policy-based tool approval for Ferrin. A PolicyClient evaluates a policy
path with a JSON input and returns a decision document; policy_approval
turns the client into a ferrin_core::generate_text::ApprovalPolicy whose
decisions (allow, deny, requires-approval, not-applicable) become
tool approval statuses, and capability_middleware restricts the tools
offered to the model through the same client. HttpPolicyClient speaks the
OPA REST Data API; RegoPolicyClient (feature rego) evaluates Rego
policies in-process with regorus.
Part of the Ferrin workspace. Design:
docs/01-architecture/18-policy-approval.md, ADR 0020.
Example
use HttpPolicyClient;
use policy_approval;
use UrlPolicy;
The policy receives { "tool": { "name" }, "args", "messages", "runtimeContext" }
and answers with an explicit decision object or a legacy { "allow": bool }.
Bare booleans are rejected. A null result is NotApplicable, which overrides
needs_approval; use with_default(policy, ApprovalStatus::user_approval()) to
require approval for unmatched rules. Shadow observe mode approves calls;
backend failures deny unless FailureMode::FallThrough is explicitly chosen.
shadow(policy).on_decision(|event| async move { ... }) observes normalized
decisions, effective decisions, enforcement mode, tool identifiers/input and
the UTC evaluation time. Auditing runs independently; errors and task panics
do not change approval. Keep the policy in an Arc and pass a clone to
.tool_approval(...), then call .flush_decisions().await before dropping it
when all audit callbacks must finish. Dropping the policy cancels pending
callbacks. The explicit on_decision_sync extension retains the synchronous
(call, status) observer and blocks approval while that callback runs.
Features
| Feature | Default | Effect |
|---|---|---|
rego |
off | RegoPolicyClient: in-process Rego evaluation with regorus |