Skip to main content

smith_protocol/
policy.rs

1use serde::{Deserialize, Serialize};
2
3/// Policy update messages delivered over the control plane.
4///
5/// Updates are applied in-order by subscribers. `reset` clears previously
6/// registered policies either globally or for a specific capability while
7/// `remove` drops a single policy by identifier. `upsert` replaces or inserts a
8/// policy definition.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(tag = "action", rename_all = "snake_case")]
11pub enum PolicyUpdate {
12    /// Insert or replace a policy definition.
13    Upsert { policy: OpaPolicy },
14    /// Remove a policy definition by id.
15    Remove { policy_id: String },
16    /// Clear all policies (or those scoped to a capability).
17    Reset { capability: Option<String> },
18}
19
20/// Declarative OPA policy delivered to the executor.
21///
22/// Policies are grouped by capability and optionally scoped to a tenant. The
23/// Rego entrypoint should return a structured object containing the fields the
24/// executor expects (see `executor::policy::PolicyDecisionEnvelope`).
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
26pub struct OpaPolicy {
27    /// Stable policy identifier used for updates/removals.
28    pub policy_id: String,
29    /// Monotonic version number supplied by the control plane.
30    pub version: u64,
31    /// Capability string (e.g. `fs.read.v1`).
32    pub capability: String,
33    /// Optional tenant scoping. `None` means policy applies to all tenants.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub tenant: Option<String>,
36    /// Lower numbers evaluate first. Defaults to `0` when omitted.
37    #[serde(default)]
38    pub priority: u32,
39    /// Fully-qualified entrypoint rule (e.g. `data.smith.allow`).
40    pub entrypoint: String,
41    /// Rego module text.
42    pub module: String,
43    /// Optional static data block to load alongside the module.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub data: Option<serde_json::Value>,
46    /// Optional execution limits override returned on allow decisions.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub limits: Option<PolicyLimits>,
49    /// Optional scope metadata forwarded on allow decisions.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub scope: Option<serde_json::Value>,
52    /// Arbitrary metadata for observability/debugging.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub metadata: Option<serde_json::Value>,
55}
56
57/// Policy-defined execution limit overrides.
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59pub struct PolicyLimits {
60    pub cpu_ms_per_100ms: u32,
61    pub mem_bytes: u64,
62    pub io_bytes: u64,
63    pub pids_max: u32,
64    pub timeout_ms: u64,
65}
66
67impl From<PolicyLimits> for crate::ExecutionLimits {
68    fn from(value: PolicyLimits) -> Self {
69        Self {
70            cpu_ms_per_100ms: value.cpu_ms_per_100ms,
71            mem_bytes: value.mem_bytes,
72            io_bytes: value.io_bytes,
73            pids_max: value.pids_max,
74            timeout_ms: value.timeout_ms,
75        }
76    }
77}