codetether_agent/provenance/
identity.rs1const GIT_EMAIL_DOMAIN: &str = "codetether.run";
2const GIT_NAME: &str = "CodeTether Agent";
3
4pub fn git_author_name(agent_identity_id: Option<&str>) -> String {
5 match agent_identity_id.and_then(sanitize_identity) {
6 Some(identity) => format!("{GIT_NAME} [{identity}]"),
7 None => GIT_NAME.to_string(),
8 }
9}
10
11pub fn git_author_email(
12 agent_identity_id: Option<&str>,
13 worker_id: Option<&str>,
14 provenance_id: Option<&str>,
15) -> String {
16 let identity = [agent_identity_id, worker_id, provenance_id]
17 .into_iter()
18 .flatten()
19 .find_map(sanitize_identity)
20 .unwrap_or_else(|| "unknown".to_string());
21 format!("agent+{identity}@{GIT_EMAIL_DOMAIN}")
22}
23
24fn sanitize_identity(value: &str) -> Option<String> {
25 let mut output = String::new();
26 let mut last_was_dash = false;
27 for ch in value.trim().chars() {
28 let lower = ch.to_ascii_lowercase();
29 if lower.is_ascii_alphanumeric() {
30 output.push(lower);
31 last_was_dash = false;
32 } else if !last_was_dash && !output.is_empty() {
33 output.push('-');
34 last_was_dash = true;
35 }
36 if output.len() >= 48 {
37 break;
38 }
39 }
40 while output.ends_with('-') {
41 output.pop();
42 }
43 (!output.is_empty()).then_some(output)
44}
45
46#[cfg(test)]
47mod tests {
48 use super::{git_author_email, git_author_name};
49
50 #[test]
51 fn derives_unique_email_from_agent_identity() {
52 let email = git_author_email(Some("user:abc-123"), None, None);
53 assert_eq!(email, "agent+user-abc-123@codetether.run");
54 }
55
56 #[test]
57 fn includes_identity_in_display_name() {
58 let name = git_author_name(Some("user:abc-123"));
59 assert_eq!(name, "CodeTether Agent [user-abc-123]");
60 }
61}