1pub const BOUNDARY_ID_LEN: usize = 8;
16
17pub fn new_boundary_id() -> String {
19 let full = uuid::Uuid::new_v4().to_string().replace('-', "");
20 full[..BOUNDARY_ID_LEN.min(full.len())].to_string()
21}
22
23pub fn new_boundary_id_with_summary(summary: Option<&str>) -> String {
28 with_summary_slug(new_boundary_id(), summary)
29}
30
31pub fn boundary_id_from_seed_with_summary(seed: &str, summary: Option<&str>) -> String {
43 let hex: String = seed
44 .chars()
45 .filter(|c| c.is_ascii_hexdigit())
46 .map(|c| c.to_ascii_lowercase())
47 .collect();
48 let id = if hex.len() >= BOUNDARY_ID_LEN {
49 hex[..BOUNDARY_ID_LEN].to_string()
50 } else {
51 let folded = seed
54 .bytes()
55 .fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
56 format!("{folded:08x}")
57 };
58 with_summary_slug(id, summary)
59}
60
61fn with_summary_slug(id: String, summary: Option<&str>) -> String {
63 match summary {
64 Some(s) if !s.is_empty() => {
65 let slug: String = s
66 .to_lowercase()
67 .chars()
68 .map(|c| if c.is_alphanumeric() { c } else { '-' })
69 .collect::<String>()
70 .split('-')
71 .filter(|s| !s.is_empty())
72 .take(3) .collect::<Vec<&str>>()
74 .join("-");
75 let slug = &slug[..slug.len().min(20)];
76 format!("{}:{}", id, slug)
77 }
78 _ => id,
79 }
80}
81
82pub fn format_boundary_marker(id: &str) -> String {
84 format!("<!-- agent:boundary:{} -->", id)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn new_boundary_id_len() {
93 assert_eq!(new_boundary_id().len(), BOUNDARY_ID_LEN);
94 assert!(new_boundary_id().chars().all(|c| c.is_ascii_hexdigit()));
95 }
96
97 #[test]
98 fn new_boundary_id_with_summary_none() {
99 assert_eq!(new_boundary_id_with_summary(None).len(), BOUNDARY_ID_LEN);
100 }
101
102 #[test]
103 fn new_boundary_id_with_summary_empty() {
104 assert_eq!(
105 new_boundary_id_with_summary(Some("")).len(),
106 BOUNDARY_ID_LEN
107 );
108 }
109
110 #[test]
111 fn new_boundary_id_with_summary_slug() {
112 let id = new_boundary_id_with_summary(Some("Boundary Fix"));
113 assert!(id.ends_with(":boundary-fix"), "got: {}", id);
114 }
115
116 #[test]
117 fn new_boundary_id_with_summary_truncate() {
118 let long = "this is a very long summary that should be truncated and clipped";
119 let id = new_boundary_id_with_summary(Some(long));
120 let (_, slug) = id.split_once(':').unwrap();
121 assert!(slug.len() <= 20, "slug too long: {}", slug);
122 }
123
124 #[test]
125 fn format_boundary_marker_renders_correctly() {
126 assert_eq!(
127 format_boundary_marker("abc123"),
128 "<!-- agent:boundary:abc123 -->"
129 );
130 }
131
132 #[test]
133 fn boundary_id_from_seed_is_deterministic() {
134 let patch_id = "2ffa57c0-24e8-441c-aca9-46e6aa6f1c2a";
138 let a = boundary_id_from_seed_with_summary(patch_id, None);
139 let b = boundary_id_from_seed_with_summary(patch_id, None);
140 assert_eq!(a, b, "same seed must yield the same boundary id");
141 assert_eq!(a.len(), BOUNDARY_ID_LEN);
142 assert_eq!(a, "2ffa57c0", "boundary derives from the seed's hex prefix");
143 assert!(a.chars().all(|c| c.is_ascii_hexdigit()));
144 }
145
146 #[test]
147 fn boundary_id_from_seed_differs_across_seeds() {
148 let a = boundary_id_from_seed_with_summary("2ffa57c0-24e8-441c-aca9-46e6aa6f1c2a", None);
149 let b = boundary_id_from_seed_with_summary("99887766-1111-2222-3333-444455556666", None);
150 assert_ne!(a, b, "different writes must not collide on one boundary");
151 }
152
153 #[test]
154 fn boundary_id_from_seed_keeps_summary_slug() {
155 let id = boundary_id_from_seed_with_summary(
156 "2ffa57c0-24e8-441c-aca9-46e6aa6f1c2a",
157 Some("Agent Doc Bugs"),
158 );
159 assert_eq!(id, "2ffa57c0:agent-doc-bugs");
160 }
161
162 #[test]
163 fn boundary_id_from_seed_without_hex_is_still_deterministic() {
164 let a = boundary_id_from_seed_with_summary("zzzz-non-hex-seed", None);
165 let b = boundary_id_from_seed_with_summary("zzzz-non-hex-seed", None);
166 assert_eq!(a, b);
167 assert_eq!(a.len(), BOUNDARY_ID_LEN);
168 }
169}