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}