Skip to main content

aap_protocol/
authorization.rs

1//! AAP Authorization — human-granted permission for an agent to act.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::crypto::{KeyPair, signable, verify_signature};
8use crate::errors::{AAPError, Result};
9
10/// AAP authorization levels.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum Level {
14    Observe    = 0,
15    Suggest    = 1,
16    Assisted   = 2,
17    Supervised = 3,
18    Autonomous = 4,
19}
20
21impl Level {
22    pub fn name(&self) -> &'static str {
23        match self {
24            Level::Observe    => "observe",
25            Level::Suggest    => "suggest",
26            Level::Assisted   => "assisted",
27            Level::Supervised => "supervised",
28            Level::Autonomous => "autonomous",
29        }
30    }
31
32    pub fn as_u8(&self) -> u8 { *self as u8 }
33}
34
35/// Maximum level allowed for physical nodes.
36/// PHYSICAL WORLD RULE: enforced in code. Not configurable.
37const PHYSICAL_MAX_LEVEL: Level = Level::Supervised;
38
39/// Authorization token granting an agent permission to act.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Authorization {
42    pub aap_version: String,
43    pub agent_id: String,
44    pub level: u8,
45    pub level_name: String,
46    pub scope: Vec<String>,
47    pub physical: bool,
48    pub granted_by: String,
49    pub granted_at: DateTime<Utc>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub expires_at: Option<DateTime<Utc>>,
52    pub session_id: String,
53    pub signature: String,
54    #[serde(skip)]
55    revoked: bool,
56}
57
58impl Authorization {
59    /// Create and sign a new Authorization.
60    /// Enforces the Physical World Rule — returns `Err(PhysicalWorldViolation)` if violated.
61    pub fn new(
62        agent_id: &str,
63        level: Level,
64        scope: Vec<String>,
65        physical: bool,
66        supervisor_kp: &KeyPair,
67        supervisor_did: &str,
68    ) -> Result<Self> {
69        // PHYSICAL WORLD RULE — enforced here. Cannot be bypassed.
70        if physical && level > PHYSICAL_MAX_LEVEL {
71            return Err(AAPError::PhysicalWorldViolation { agent_id: agent_id.into() });
72        }
73
74        let mut auth = Self {
75            aap_version: "0.1".into(),
76            agent_id: agent_id.into(),
77            level: level.as_u8(),
78            level_name: level.name().into(),
79            scope,
80            physical,
81            granted_by: supervisor_did.into(),
82            granted_at: Utc::now(),
83            expires_at: None,
84            session_id: Uuid::new_v4().to_string(),
85            signature: String::new(),
86            revoked: false,
87        };
88
89        let v = serde_json::to_value(&auth)?;
90        let data = signable(&v)?;
91        auth.signature = supervisor_kp.sign(&data);
92        Ok(auth)
93    }
94
95    pub fn revoke(&mut self) { self.revoked = true; }
96    pub fn is_revoked(&self) -> bool { self.revoked }
97    pub fn is_expired(&self) -> bool {
98        self.expires_at.map(|e| Utc::now() > e).unwrap_or(false)
99    }
100    pub fn is_valid(&self) -> bool { !self.is_revoked() && !self.is_expired() }
101
102    pub fn check(&self) -> Result<()> {
103        if self.is_revoked() {
104            return Err(AAPError::Revocation { id: self.session_id.clone() });
105        }
106        if self.is_expired() {
107            return Err(AAPError::Revocation {
108                id: format!("{} (expired)", self.session_id),
109            });
110        }
111        Ok(())
112    }
113
114    pub fn verify(&self, supervisor_public_key_b64: &str) -> Result<()> {
115        let v = serde_json::to_value(self)?;
116        let data = signable(&v)?;
117        verify_signature(supervisor_public_key_b64, &data, &self.signature)
118    }
119}