Skip to main content

entelix_policy/
lib.rs

1//! # entelix-policy
2//!
3//! Multi-tenant operational primitives that `LangChain` / `LangGraph` leave to
4//! the host: token-bucket rate limiting, bidirectional PII redaction
5//! (F5 mitigation), `rust_decimal`-backed transactional cost accounting
6//! (F4 mitigation), composite quota enforcement, and the per-tenant
7//! aggregate ([`TenantPolicy`]) plus the runtime registry
8//! ([`PolicyRegistry`]) that indexes them by `tenant_id`.
9//!
10//! ## Surface in one screen
11//!
12//! - [`RateLimiter`] (trait) + [`TokenBucketLimiter`] — async,
13//!   per-key, time-injectable for deterministic tests.
14//! - [`PiiRedactor`] (trait) + [`RegexRedactor`] — runs on both
15//!   `pre_request` and `post_response` so leaks can't slip past
16//!   in either direction.
17//! - [`CostMeter`] + [`PricingTable`] / [`ModelPricing`] — `rust_decimal`
18//!   for float-free arithmetic; charges are recorded only after the
19//!   response decoder succeeds (transactional — F4).
20//! - [`QuotaLimiter`] — composite: rate (RPS) + budget ceiling
21//!   (per-tenant cumulative spend cap).
22//! - [`TenantPolicy`] — per-tenant aggregate of optional handles to
23//!   the four primitives above.
24//! - [`PolicyRegistry`] — `DashMap<tenant_id, Arc<TenantPolicy>>`
25//!   with a fallback default policy.
26//! - [`PolicyLayer`] — `tower::Layer<S>` that wires every primitive
27//!   into both `Service<ModelInvocation>` and `Service<ToolInvocation>`
28//!   pipelines. Compose via `ChatModel::layer(PolicyLayer::new(mgr))`
29//!   for model calls and `ToolRegistry::layer(PolicyLayer::new(mgr))`
30//!   for tool calls — same struct on both sides.
31//!
32//! ## Layer lifecycle (model calls)
33//!
34//! - **before inner.call** (`Service<ModelInvocation>`):
35//!   1. `PiiRedactor::redact_request` — outbound scrub.
36//!   2. `QuotaLimiter::check_pre_request` — rate + budget gate.
37//!      Returns `Error::Provider { status: 429 | 402, ... }` on refusal.
38//! - **after inner.call**:
39//!   1. `PiiRedactor::redact_response` — inbound scrub.
40//!   2. `CostMeter::charge` — transactional charge (F4 — only here,
41//!      after a successful inner call).
42//!
43//! ## Layer lifecycle (tool calls)
44//!
45//! - **before inner.call** (`Service<ToolInvocation>`):
46//!   1. `PiiRedactor::redact_json(input)` — scrub tool input JSON.
47//! - **after inner.call**:
48//!   1. `PiiRedactor::redact_json(output)` — scrub tool output JSON.
49//!
50//! ## Tenant scoping
51//!
52//! Every primitive looks up state by `ExecutionContext::tenant_id()`
53//!. A request without an explicit tenant uses the
54//! [`entelix_core::DEFAULT_TENANT_ID`] scope; the default
55//! tenant gets the [`PolicyRegistry`]'s default policy (typically
56//! "no policy" — pass-through).
57
58#![cfg_attr(docsrs, feature(doc_cfg))]
59#![doc(html_root_url = "https://docs.rs/entelix-policy/0.5.3")]
60#![deny(missing_docs)]
61#![allow(
62    clippy::missing_errors_doc,
63    clippy::missing_panics_doc,
64    clippy::missing_const_for_fn
65)]
66
67mod cost;
68mod error;
69mod layer;
70mod pii;
71mod quota;
72mod rate_limit;
73mod tenant;
74
75pub use cost::{
76    CostMeter, DEFAULT_MAX_TENANTS, MAX_WARNED_MODELS, ModelPricing, PricingTable,
77    UnknownModelPolicy, UnknownModelSink,
78};
79pub use error::{PolicyError, PolicyResult};
80pub use layer::{PolicyLayer, PolicyService};
81pub use pii::{PiiPattern, PiiRedactor, RegexRedactor, default_pii_patterns, luhn_valid};
82pub use quota::{Budget, QuotaLimiter};
83pub use rate_limit::{RateLimiter, TokenBucketLimiter};
84pub use tenant::{PolicyRegistry, TenantPolicy};