use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub type IntentId = String;
pub type AgentId = String;
pub type SessionId = String;
pub type Principal = String;
pub const TAG_PIN_STATE: &str = "asg:pin-state";
pub const TAG_GIT_REVISION: &str = "git:";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Intent {
pub id: IntentId,
pub category: IntentCategory,
pub description: String,
pub tags: Vec<String>,
pub parent_intent: Option<IntentId>,
pub lifecycle: IntentLifecycle,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum IntentCategory {
Explore,
Refine,
Fix,
Rollback,
Checkpoint,
Merge,
Migrate,
Plan,
Taint,
Untaint,
Quarantine,
Unquarantine,
Watch,
Unwatch,
PolicyPropose,
PolicyRatify,
PolicySupersede,
PolicySign,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IntentLifecycle {
pub status: IntentStatus,
pub assigned_to: Vec<AgentId>,
pub resolution: Option<Resolution>,
pub notification: Option<NotificationPolicy>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum IntentStatus {
Proposed,
Authorized,
InProgress,
Completed,
Failed,
Blocked,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Resolution {
pub summary: String,
pub deviations: Vec<Deviation>,
pub commits: Vec<String>,
pub branches_explored: Vec<String>,
pub outcome: Outcome,
pub confidence: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Deviation {
pub description: String,
pub reason: String,
pub impact: DeviationImpact,
pub follow_up: Option<IntentId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DeviationImpact {
Low,
Medium,
High,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Outcome {
Fulfilled,
PartiallyFulfilled,
Failed,
Deferred,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NotificationPolicy {
pub urgency: Urgency,
pub audience: Vec<Principal>,
pub format_hint: FormatHint,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Urgency {
Routine,
Priority,
Critical,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FormatHint {
Summary,
Detailed,
DiffOnly,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Authority {
pub principal: Principal,
pub scope: AuthScope,
pub granted_at: DateTime<Utc>,
pub expires: Option<DateTime<Utc>>,
pub delegation_chain: Vec<DelegationLink>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AuthScope {
Intent(IntentId),
Branch(String),
Path(String),
Wildcard,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DelegationLink {
pub from: Principal,
pub to: Principal,
pub scope: AuthScope,
pub granted_at: DateTime<Utc>,
pub expires: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCall {
pub tool_name: String,
pub arguments: serde_json::Value,
pub result: Option<String>,
pub timestamp: DateTime<Utc>,
}
impl Intent {
pub fn new(category: IntentCategory, description: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
category,
description: description.into(),
tags: Vec::new(),
parent_intent: None,
lifecycle: IntentLifecycle {
status: IntentStatus::Proposed,
assigned_to: Vec::new(),
resolution: None,
notification: None,
},
}
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_parent(mut self, parent_id: IntentId) -> Self {
self.parent_intent = Some(parent_id);
self
}
}
impl Authority {
pub fn simple(principal: impl Into<String>) -> Self {
Self {
principal: principal.into(),
scope: AuthScope::Wildcard,
granted_at: Utc::now(),
expires: None,
delegation_chain: Vec::new(),
}
}
pub fn for_intent(principal: impl Into<String>, intent_id: IntentId) -> Self {
Self {
principal: principal.into(),
scope: AuthScope::Intent(intent_id),
granted_at: Utc::now(),
expires: None,
delegation_chain: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plan_variant_serializes_as_native() {
let c = IntentCategory::Plan;
let json = serde_json::to_string(&c).unwrap();
assert_eq!(json, "\"Plan\"");
}
#[test]
fn plan_variant_deserializes_from_native_form() {
let c: IntentCategory = serde_json::from_str("\"Plan\"").unwrap();
assert_eq!(c, IntentCategory::Plan);
}
#[test]
fn custom_plan_string_still_round_trips_as_custom() {
let c = IntentCategory::Custom("Plan".to_string());
let json = serde_json::to_string(&c).unwrap();
assert_eq!(json, "{\"Custom\":\"Plan\"}");
let back: IntentCategory = serde_json::from_str(&json).unwrap();
assert_eq!(back, IntentCategory::Custom("Plan".to_string()));
assert_ne!(back, IntentCategory::Plan);
}
#[test]
fn all_native_variants_round_trip() {
for c in [
IntentCategory::Explore,
IntentCategory::Refine,
IntentCategory::Fix,
IntentCategory::Rollback,
IntentCategory::Checkpoint,
IntentCategory::Merge,
IntentCategory::Migrate,
IntentCategory::Plan,
IntentCategory::Taint,
IntentCategory::Untaint,
IntentCategory::Quarantine,
IntentCategory::Unquarantine,
IntentCategory::Watch,
IntentCategory::Unwatch,
IntentCategory::PolicyPropose,
IntentCategory::PolicyRatify,
IntentCategory::PolicySupersede,
IntentCategory::PolicySign,
] {
let json = serde_json::to_string(&c).unwrap();
let back: IntentCategory = serde_json::from_str(&json).unwrap();
assert_eq!(c, back, "round-trip failed for {:?}", c);
}
}
#[test]
fn policy_variants_serialize_as_native() {
assert_eq!(
serde_json::to_string(&IntentCategory::PolicyPropose).unwrap(),
"\"PolicyPropose\""
);
assert_eq!(
serde_json::to_string(&IntentCategory::PolicyRatify).unwrap(),
"\"PolicyRatify\""
);
assert_eq!(
serde_json::to_string(&IntentCategory::PolicySupersede).unwrap(),
"\"PolicySupersede\""
);
assert_eq!(
serde_json::to_string(&IntentCategory::PolicySign).unwrap(),
"\"PolicySign\""
);
}
#[test]
fn custom_policy_strings_still_round_trip_as_custom() {
for name in [
"PolicyPropose",
"PolicyRatify",
"PolicySupersede",
"PolicySign",
] {
let c = IntentCategory::Custom(name.to_string());
let json = serde_json::to_string(&c).unwrap();
assert_eq!(json, format!("{{\"Custom\":\"{}\"}}", name));
let back: IntentCategory = serde_json::from_str(&json).unwrap();
assert_eq!(back, IntentCategory::Custom(name.to_string()));
}
}
#[test]
fn custom_taint_string_still_round_trips_as_custom() {
for name in [
"Taint",
"Untaint",
"Quarantine",
"Unquarantine",
"Watch",
"Unwatch",
] {
let c = IntentCategory::Custom(name.to_string());
let json = serde_json::to_string(&c).unwrap();
assert_eq!(json, format!("{{\"Custom\":\"{}\"}}", name));
let back: IntentCategory = serde_json::from_str(&json).unwrap();
assert_eq!(back, IntentCategory::Custom(name.to_string()));
}
}
#[test]
fn taint_variants_serialize_as_native() {
assert_eq!(
serde_json::to_string(&IntentCategory::Taint).unwrap(),
"\"Taint\""
);
assert_eq!(
serde_json::to_string(&IntentCategory::Quarantine).unwrap(),
"\"Quarantine\""
);
assert_eq!(
serde_json::to_string(&IntentCategory::Watch).unwrap(),
"\"Watch\""
);
}
}