Skip to main content

auths_id/
agent_identity.rs

1//! Agent identity config + TOML preview types.
2//!
3//! The standalone-`icp` agent provisioning (`provision_agent_identity`) was retired
4//! in Epic E: an agent is now a KERI **delegated identifier** (`dip` delegated by a
5//! root/org, anchored by the root's `ixn`), created with `auths id agent add`
6//! (SDK `agents::add`) — not a standalone root identity stamped with an `Agent`
7//! attestation. What remains here is the configuration shape and the
8//! `auths-agent.toml` formatter still used by the `init` dry-run preview.
9
10use std::path::PathBuf;
11
12use auths_core::storage::keychain::IdentityDID;
13
14/// Storage mode for an agent identity.
15#[derive(Debug, Clone)]
16pub enum AgentStorageMode {
17    /// Persistent storage at a filesystem path.
18    /// Defaults to `~/.auths-agent` if `repo_path` is `None`.
19    Persistent {
20        /// Repository path; `None` selects the default `~/.auths-agent`.
21        repo_path: Option<PathBuf>,
22    },
23    /// In-memory storage for ephemeral/stateless containers (Fargate, Docker).
24    /// Agent identity lives only for the process lifetime.
25    InMemory,
26}
27
28/// Configuration describing an agent identity (for previews / config files).
29#[derive(Debug, Clone)]
30pub struct AgentProvisioningConfig {
31    /// Human-readable agent name (e.g., "ci-bot", "release-agent").
32    pub agent_name: String,
33    /// Capabilities to grant (e.g., `["sign_commit", "pr:create"]`).
34    pub capabilities: Vec<String>,
35    /// Duration in seconds until expiration (per RFC 6749).
36    pub expires_in: Option<u64>,
37    /// DID of the root/org that delegates this agent.
38    pub delegated_by: Option<IdentityDID>,
39    /// Storage mode (persistent or ephemeral).
40    pub storage_mode: AgentStorageMode,
41}
42
43/// Render an `auths-agent.toml` preview for the given agent config.
44///
45/// Args:
46/// * `did`: The agent's `did:keri:` (or a `<pending>` placeholder in a dry run).
47/// * `key_alias`: The keychain alias the agent key is stored under.
48/// * `config`: The agent configuration to render.
49///
50/// Usage:
51/// ```ignore
52/// let toml = format_agent_toml("did:keri:E...", "agent-key", &config);
53/// ```
54pub fn format_agent_toml(did: &str, key_alias: &str, config: &AgentProvisioningConfig) -> String {
55    let caps = config
56        .capabilities
57        .iter()
58        .map(|c| format!("\"{}\"", c))
59        .collect::<Vec<_>>()
60        .join(", ");
61
62    let mut out = format!(
63        "# Auths Agent Configuration\n\
64         # An agent is a KERI delegated identifier (dip) — create with `auths id agent add`\n\n\
65         [agent]\n\
66         name = \"{}\"\n\
67         did = \"{}\"\n\
68         key_alias = \"{}\"\n\
69         signer_type = \"Agent\"\n",
70        config.agent_name, did, key_alias,
71    );
72
73    if let Some(ref delegator) = config.delegated_by {
74        out.push_str(&format!("delegated_by = \"{}\"\n", delegator));
75    }
76
77    out.push_str(&format!("\n[capabilities]\ngranted = [{}]\n", caps));
78
79    if let Some(secs) = config.expires_in {
80        out.push_str(&format!("\n[expiry]\nexpires_in = {}\n", secs));
81    }
82
83    out
84}
85
86#[cfg(test)]
87#[allow(clippy::disallowed_methods)] // INVARIANT: tests construct IdentityDID via new_unchecked with literal DIDs
88mod tests {
89    use super::*;
90
91    #[test]
92    fn format_agent_toml_with_all_fields() {
93        let config = AgentProvisioningConfig {
94            agent_name: "ci-bot".to_string(),
95            capabilities: vec!["sign_commit".to_string(), "pr:create".to_string()],
96            expires_in: Some(86400),
97            delegated_by: Some(IdentityDID::new_unchecked("did:keri:Eabc123")),
98            storage_mode: AgentStorageMode::Persistent { repo_path: None },
99        };
100        let toml = format_agent_toml("did:keri:Eagent", "agent-key", &config);
101        assert!(toml.contains("name = \"ci-bot\""));
102        assert!(toml.contains("did = \"did:keri:Eagent\""));
103        assert!(toml.contains("delegated_by = \"did:keri:Eabc123\""));
104        assert!(toml.contains("\"sign_commit\", \"pr:create\""));
105        assert!(toml.contains("expires_in = 86400"));
106    }
107
108    #[test]
109    fn format_agent_toml_minimal() {
110        let config = AgentProvisioningConfig {
111            agent_name: "solo".to_string(),
112            capabilities: vec![],
113            expires_in: None,
114            delegated_by: None,
115            storage_mode: AgentStorageMode::InMemory,
116        };
117        let toml = format_agent_toml("did:keri:E1", "k", &config);
118        assert!(!toml.contains("delegated_by"));
119        assert!(!toml.contains("[expiry]"));
120    }
121
122    #[test]
123    fn init_dryrun_shows_delegated_agent() {
124        // The `init --agent` dry-run renders this preview; after Epic E it must
125        // present a *delegated* identifier (not a standalone identity) and name the
126        // delegating root when one is supplied.
127        let config = AgentProvisioningConfig {
128            agent_name: "deploy-bot".to_string(),
129            capabilities: vec!["sign_commit".to_string()],
130            expires_in: None,
131            delegated_by: Some(IdentityDID::new_unchecked("did:keri:Eroot")),
132            storage_mode: AgentStorageMode::Persistent { repo_path: None },
133        };
134        let toml = format_agent_toml("did:keri:E<pending>", "agent-key", &config);
135        assert!(
136            toml.contains("delegated identifier"),
137            "dry-run must frame the agent as a delegated identifier"
138        );
139        assert!(
140            toml.contains("delegated_by = \"did:keri:Eroot\""),
141            "dry-run must name the delegating root"
142        );
143    }
144}