pub fn is_component(name: &str) -> bool {
!name.is_empty()
&& name != "."
&& name != ".."
&& !name.contains('/')
&& !name.contains('\\')
&& !name.contains('\0')
}
#[derive(Debug, thiserror::Error)]
#[error(
"agent id {0:?} is not a single path component — an agent id is its branch name, \
the hyphenated descent `<a>-<b>-…` (ARCH §2.3); pass the id exactly as \
`lernie prompt` / `lernie dispatch` printed it"
)]
pub struct NotAnAgentId(String);
pub fn require_agent_id(id: &str) -> Result<(), NotAnAgentId> {
if is_component(id) {
return Ok(());
}
Err(NotAnAgentId(id.to_owned()))
}
pub fn pool<S: AsRef<str>>(names: &[S]) -> String {
if names.is_empty() {
return "(none)".to_string();
}
names
.iter()
.map(AsRef::as_ref)
.collect::<Vec<_>>()
.join(", ")
}
#[cfg(test)]
mod tests {
use super::{is_component, pool, require_agent_id};
#[test]
fn a_plain_name_is_one_component() {
assert!(is_component("20260101-a1"));
assert!(require_agent_id("20260101-a1-20260102-b2").is_ok());
}
#[test]
fn escapes_and_empties_are_declined() {
for bad in [
"",
".",
"..",
"../../victim/pwned",
"/etc/lernie",
"a\\b",
"a\0b",
] {
assert!(!is_component(bad), "{bad:?} must not pass");
assert!(require_agent_id(bad).is_err(), "{bad:?} must be declined");
}
}
#[test]
fn the_decline_names_the_id_and_the_rule() {
let err = require_agent_id("../../victim/pwned").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("\"../../victim/pwned\""), "{msg}");
assert!(msg.contains("single path component"), "{msg}");
assert!(msg.contains("§2.3"), "{msg}");
}
#[test]
fn a_pool_renders_joined_and_an_empty_pool_reads_as_none() {
assert_eq!(
pool(&["default", "strict", "lone"]),
"default, strict, lone"
);
assert_eq!(pool(&["only".to_string()]), "only");
assert_eq!(pool::<&str>(&[]), "(none)");
}
}