Skip to main content

openagent_auth_protocol/
types.rs

1// Copyright (c) 2026 L1fe Labs, Inc. — Apache-2.0 OR MIT
2//! Shared types for the OAAP protocol.
3//!
4//! Wire message types live in [`crate::message`]. This module contains the
5//! supporting types referenced by messages and the post-handshake context.
6
7use serde::{Deserialize, Serialize};
8
9/// Conformance levels for entity classification (Bioagentic).
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum ConformanceLevel {
12    /// L0: Passive signals only (UA, TLS fingerprint).
13    #[serde(rename = "L0")]
14    L0,
15    /// L1: Behavioral analysis (18-dim feature extraction).
16    #[serde(rename = "L1")]
17    L1,
18    /// L2: Full cryptographic proof (Ed25519 / FIDO2).
19    #[serde(rename = "L2")]
20    L2,
21}
22
23impl ConformanceLevel {
24    /// Returns the numeric ordering (L0=0, L1=1, L2=2).
25    pub fn level(&self) -> u8 {
26        match self {
27            Self::L0 => 0,
28            Self::L1 => 1,
29            Self::L2 => 2,
30        }
31    }
32
33    /// Returns true if `self` satisfies the `required` level.
34    pub fn satisfies(&self, required: &Self) -> bool {
35        self.level() >= required.level()
36    }
37}
38
39impl std::fmt::Display for ConformanceLevel {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::L0 => write!(f, "L0"),
43            Self::L1 => write!(f, "L1"),
44            Self::L2 => write!(f, "L2"),
45        }
46    }
47}
48
49/// Minimal DID document fragment for the handshake.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct DidDocument {
52    /// Canonical DID (e.g. `did:oas:l1fe:agent:my-bot`).
53    pub id: String,
54    /// Multibase-encoded Ed25519 verification key.
55    pub verification_key: String,
56    /// Entity kind (`agent`, `tool`, `service`, etc.).
57    pub kind: String,
58    /// Optional parent DID for lineage.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub parent: Option<String>,
61}
62
63/// Cryptographic lineage proof linking an agent to its human root.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct LineageProof {
66    /// Chain of DID-to-DID attestations from the agent back to HMR.
67    pub chain: Vec<LineageLink>,
68}
69
70/// A single link in the lineage chain.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LineageLink {
73    /// DID of the parent (delegator).
74    pub from: String,
75    /// DID of the child (delegatee).
76    pub to: String,
77    /// Ed25519 signature by `from` over the delegation assertion.
78    pub signature: String,
79    /// ISO-8601 timestamp of the delegation.
80    pub issued_at: String,
81}
82
83/// Server discovery response at `GET /.well-known/openagent`.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct DiscoveryDocument {
86    /// Auth endpoint path (default: `/.well-known/openagent/auth`).
87    pub auth_endpoint: String,
88    /// Supported protocol versions.
89    pub supported_versions: Vec<u32>,
90    /// Server's DID.
91    pub server_did: String,
92    /// Minimum conformance level required.
93    pub required_conformance_level: ConformanceLevel,
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn conformance_level_satisfies() {
102        assert!(ConformanceLevel::L2.satisfies(&ConformanceLevel::L0));
103        assert!(ConformanceLevel::L2.satisfies(&ConformanceLevel::L1));
104        assert!(ConformanceLevel::L2.satisfies(&ConformanceLevel::L2));
105        assert!(ConformanceLevel::L1.satisfies(&ConformanceLevel::L0));
106        assert!(!ConformanceLevel::L0.satisfies(&ConformanceLevel::L1));
107    }
108
109    #[test]
110    fn conformance_level_display() {
111        assert_eq!(ConformanceLevel::L0.to_string(), "L0");
112        assert_eq!(ConformanceLevel::L1.to_string(), "L1");
113        assert_eq!(ConformanceLevel::L2.to_string(), "L2");
114    }
115
116    #[test]
117    fn discovery_document_serializes() {
118        let doc = DiscoveryDocument {
119            auth_endpoint: "/.well-known/openagent/auth".into(),
120            supported_versions: vec![1],
121            server_did: "did:oas:l1fe:service:api".into(),
122            required_conformance_level: ConformanceLevel::L2,
123        };
124        let json = serde_json::to_string(&doc);
125        assert!(json.is_ok());
126        let parsed: Result<DiscoveryDocument, _> = serde_json::from_str(&json.unwrap_or_default());
127        assert!(parsed.is_ok());
128    }
129
130    #[test]
131    fn did_document_round_trip() {
132        let doc = DidDocument {
133            id: "did:oas:l1fe:agent:test".into(),
134            verification_key: "z6Mk...".into(),
135            kind: "agent".into(),
136            parent: Some("did:oas:l1fe:hmr:alice".into()),
137        };
138        let json = serde_json::to_string(&doc);
139        assert!(json.is_ok());
140    }
141}