use serde::{Deserialize, Serialize};
use crate::generated::types::{Dimension, EffectVerb, Lever, PolicyMode, TargetClass, Verdict};
pub const MODE_ENFORCE: &str = "enforce";
pub const MODE_MONITOR: &str = "monitor";
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Event {
#[serde(default)]
pub event_type: String,
#[serde(default)]
pub session_id: String,
#[serde(default)]
pub tool_use_id: String,
#[serde(default)]
pub tool_name: String,
#[serde(default)]
pub tool_input: serde_json::Value,
#[serde(default)]
pub tool_result: Option<serde_json::Value>,
#[serde(default)]
pub agent: Option<AgentContext>,
#[serde(default)]
pub binding: serde_json::Value,
#[serde(default)]
pub env: Option<EventEnv>,
#[serde(default)]
pub session: Option<SessionFacts>,
#[serde(default)]
pub spend_delta: Option<SpendDelta>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct AgentContext {
#[serde(default)]
pub agent_id: Option<String>,
#[serde(default)]
pub agent_row_id: Option<String>,
#[serde(default)]
pub agent_type: Option<String>,
#[serde(default)]
pub environment: Option<String>,
#[serde(default)]
pub function: Option<String>,
#[serde(default)]
pub principal: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct EventEnv {
#[serde(default)]
pub home: Option<String>,
#[serde(default)]
pub cwd: Option<String>,
#[serde(default)]
pub path_dirs: Vec<String>,
#[serde(default)]
pub additional_dirs: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct SessionFacts {
#[serde(default)]
pub elapsed_ms: Option<i64>,
#[serde(default)]
pub tool_calls: Option<i64>,
#[serde(default)]
pub spend_micro_usd: Option<i64>,
#[serde(default)]
pub tokens: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct SpendDelta {
#[serde(default)]
pub tokens: Option<i64>,
#[serde(default)]
pub micro_usd: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Decision {
pub verdict: Verdict,
pub artifact_id: Option<String>,
pub atom_id: Option<String>,
pub policy_public_id: Option<String>,
pub dimension: Option<Dimension>,
pub mode: Option<PolicyMode>,
pub tier: Option<i64>,
pub reason: String,
pub would_have_verdict: Option<Verdict>,
pub inconclusive_facts: Vec<String>,
pub rewrite: Option<Rewrite>,
pub hold: Option<HoldRequest>,
pub effects: Vec<Effect>,
pub undecided: bool,
pub unknown: Vec<UnknownCommand>,
pub anomalies: Vec<Anomaly>,
pub ground_key: Option<String>,
pub warnings: Vec<String>,
}
impl Default for Decision {
fn default() -> Self {
Self {
verdict: Verdict::Allow,
artifact_id: None,
atom_id: None,
policy_public_id: None,
dimension: None,
mode: None,
tier: None,
reason: String::new(),
would_have_verdict: None,
inconclusive_facts: Vec::new(),
rewrite: None,
hold: None,
effects: Vec::new(),
undecided: true,
unknown: Vec::new(),
anomalies: Vec::new(),
ground_key: None,
warnings: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Effect {
pub verb: EffectVerb,
pub target_class: TargetClass,
#[serde(default)]
pub attrs: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UnknownCommand {
pub shape: i64,
pub reason: String,
pub command: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Anomaly {
pub code: String,
pub artifact_id: Option<String>,
pub atom_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Rewrite {
pub lever: Lever,
pub artifact_id: Option<String>,
pub steer_instruction: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HoldRequest {
pub resolve: Option<String>,
pub directive_template_id: Option<String>,
pub max_attempts: Option<i64>,
pub timeout_s: Option<i64>,
pub on_timeout: Option<Verdict>,
pub verdict_on_approve: Option<Verdict>,
pub verdict_on_reject: Option<Verdict>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct StateLayout {
pub c: usize,
pub f: usize,
pub t: usize,
pub a: usize,
pub run: bool,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct SessionState {
#[serde(default)]
pub c: Vec<i64>,
#[serde(default)]
pub f: Vec<bool>,
#[serde(default)]
pub t: Vec<i64>,
#[serde(default)]
pub a: Vec<i64>,
#[serde(default)]
pub run: Option<RunState>,
}
impl SessionState {
pub fn blank(layout: &StateLayout) -> Self {
Self {
c: vec![0; layout.c],
f: vec![false; layout.f],
t: vec![0; layout.t],
a: vec![0; layout.a],
run: layout.run.then(RunState::default),
}
}
pub fn matches(&self, layout: &StateLayout) -> bool {
self.c.len() == layout.c
&& self.f.len() == layout.f
&& self.t.len() == layout.t
&& self.a.len() == layout.a
&& self.run.is_some() == layout.run
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct RunState {
pub shape: Option<String>,
pub len: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SkippedItem {
pub kind: String,
pub id: String,
pub reason: String,
}
pub const SKIP_UNKNOWN_KIND: &str = "unknown_kind";
pub const SKIP_BODY_PARSE_ERROR: &str = "body_parse_error";
pub const SKIP_BAD_PATTERN: &str = "bad_pattern";
pub const SKIP_BELOW_FLOOR: &str = "below_floor";
pub const SKIP_FLOAT_PRESENT: &str = "float_present";
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Classification {
pub effects: Vec<Effect>,
pub unknown: Vec<UnknownCommand>,
pub simple: Vec<SimpleCommand>,
pub paths: Vec<ClassifiedPath>,
pub urls: Vec<ClassifiedUrl>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct SimpleCommand {
pub program: String,
pub argv: Vec<String>,
pub raw: String,
pub redirects: Vec<(String, String)>,
pub raw_argv: Vec<String>,
}
impl SimpleCommand {
pub fn as_value(&self) -> serde_json::Value {
serde_json::json!({ "program": self.program, "argv": self.argv })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClassifiedPath {
pub class: TargetClass,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClassifiedUrl {
pub value: String,
pub host: String,
pub tld: String,
pub scheme: String,
pub boundary: UrlBoundary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UrlBoundary {
Internal,
External,
}
impl UrlBoundary {
pub fn as_str(self) -> &'static str {
match self {
UrlBoundary::Internal => "internal",
UrlBoundary::External => "external",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Contribution {
pub artifact_id: Option<String>,
pub atom_id: Option<String>,
pub policy_public_id: Option<String>,
pub dimension: Option<Dimension>,
pub mode: PolicyMode,
pub tier: Option<i64>,
pub verdict: Verdict,
pub reason: String,
pub inconclusive: Vec<String>,
pub anomalies: Vec<String>,
pub hold: Option<HoldRequest>,
pub exception_ground_key: Option<String>,
pub lever: Option<Lever>,
pub steer_instruction: Option<String>,
}
impl Contribution {
pub fn is_enforcing(&self) -> bool {
self.mode.as_str() == MODE_ENFORCE
}
}
#[derive(Debug)]
pub struct EvalContext<'a> {
pub event: &'a Event,
pub classification: &'a Classification,
pub facts: &'a super::facts::FactSet,
pub now_ms: i64,
pub inconclusive: Vec<String>,
pub warnings: Vec<String>,
}
impl<'a> EvalContext<'a> {
pub fn new(
event: &'a Event,
classification: &'a Classification,
facts: &'a super::facts::FactSet,
now_ms: i64,
) -> Self {
Self {
event,
classification,
facts,
now_ms,
inconclusive: Vec::new(),
warnings: Vec::new(),
}
}
pub fn fork(&self) -> EvalContext<'a> {
EvalContext::new(self.event, self.classification, self.facts, self.now_ms)
}
pub fn note_inconclusive(&mut self, fact_id: &str) {
if !self.inconclusive.iter().any(|f| f == fact_id) {
self.inconclusive.push(fact_id.to_string());
}
}
pub fn warn(&mut self, message: impl Into<String>) {
let message = message.into();
if !self.warnings.contains(&message) {
self.warnings.push(message);
}
}
pub fn merge_warnings(&mut self, child: &EvalContext<'_>) {
for warning in &child.warnings {
self.warn(warning.clone());
}
}
}