1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! Policy-based tool approval for Ferrin.
//!
//! A [`PolicyClient`] evaluates a policy path with a JSON input and returns
//! the raw decision document. [`policy_approval`] turns a client into an
//! [`ApprovalPolicy`](ferrin_core::generate_text::ApprovalPolicy): the tool
//! call, its input, the messages and the tools context become the policy
//! input, and the normalized [`PolicyDecision`] becomes the approval status.
//! [`capability_middleware`] filters the tools offered to the model through
//! the same client. [`shadow`] observes decisions without enforcing them and
//! [`with_default`] gives every call a decision.
//!
//! Clients: [`HttpPolicyClient`] speaks the OPA REST Data API
//! (`POST /v1/data/<path>`); [`RegoPolicyClient`] (feature `rego`) evaluates
//! Rego policies in-process with `regorus`. [`policy_client`] adapts a
//! closure, for tests and static rules.
//!
//! Design: `docs/01-architecture/18-policy-approval.md`, ADR 0020.
//!
//! # Attribution
//!
//! The decision document format, its normalization rules and the shadow and
//! capability patterns are derived from the Vercel AI SDK (Apache-2.0,
//! Copyright 2023 Vercel, Inc.) and reimplemented in Rust. See the `NOTICE`
//! file in the crate root.
//!
//! # Examples
//!
//! ```
//! use ferrin_core::generate_text::ApprovalStatus;
//! use ferrin_policy::PolicyDecision;
//! use ferrin_policy::policy_approval;
//! use ferrin_policy::policy_client;
//! use serde_json::json;
//!
//! // A decision document as returned by a policy server or a Rego rule.
//! let decision = PolicyDecision::normalize(&json!({
//! "decision": "requires-approval",
//! "reason": "writes outside the workspace"
//! }));
//! assert_eq!(
//! decision.into_approval(),
//! Some(ApprovalStatus::user_approval().with_reason("writes outside the workspace"))
//! );
//!
//! // A static in-process client; pass the policy to
//! // `generate_text(..).tool_approval(policy)`.
//! let client = policy_client(|_path, input| {
//! Ok(json!({ "decision": if input["tool"]["name"] == "delete_file" { "deny" } else { "allow" } }))
//! });
//! let _policy = policy_approval(client, "ferrin/tools/decision");
//! ```
pub use FailureMode;
pub use PolicyApproval;
pub use ToInputFn;
pub use WithDefault;
pub use default_input;
pub use policy_approval;
pub use with_default;
pub use CapabilityInputFn;
pub use CapabilityMiddleware;
pub use capability_middleware;
pub use default_capability_input;
pub use parse_allowlist;
pub use PolicyClient;
pub use PolicyClientFn;
pub use SharedPolicyClient;
pub use policy_client;
pub use PolicyDecision;
pub use UNRECOGNIZED_DECISION;
pub use PolicyError;
pub use DEFAULT_MAX_RESPONSE_BYTES;
pub use HttpPolicyClient;
pub use HttpPolicyClientBuilder;
pub use RegoPolicyClient;
pub use RegoPolicyClientBuilder;
pub use Enforcement;
pub use OnDecisionFn;
pub use Shadow;
pub use shadow;