Skip to main content

systemprompt_identifiers/
brain.rs

1//! Brain identifiers.
2//!
3//! [`BrainFactId`] is `m_<uuid>`: the prefix is user-visible in recalled
4//! memory blocks and response headers, so it is minted by a hand-written
5//! `generate()` and checked on parse. Kinds, topics, interests and workflows
6//! are declared keys from `services/brain/**`, checked to 1-64 characters of
7//! lowercase ASCII letters, digits, `_`, `.` and `-`. Extractions,
8//! proposals, recalls, disputes and fact revisions are UUID-backed.
9//! [`PipelineVersion`] is the 16 lowercase hex characters of the pipeline
10//! digest.
11//!
12//! Copyright (c) systemprompt.io — Business Source License 1.1.
13//! See <https://systemprompt.io> for licensing details.
14
15use crate::error::IdValidationError;
16
17crate::define_id!(BrainFactId, checked, validate_fact_id);
18crate::define_id!(FactKindId, checked, |value| validate_key(
19    "FactKindId",
20    value
21));
22crate::define_id!(TopicId, checked, |value| validate_key("TopicId", value));
23crate::define_id!(InterestId, checked, |value| validate_key(
24    "InterestId",
25    value
26));
27crate::define_id!(WorkflowId, checked, |value| validate_key(
28    "WorkflowId",
29    value
30));
31crate::define_id!(BrainExtractionId, uuid);
32crate::define_id!(BrainProposalId, uuid);
33crate::define_id!(BrainRecallId, uuid);
34crate::define_id!(BrainDisputeId, uuid);
35crate::define_id!(BrainFactRevisionId, uuid);
36crate::define_id!(PipelineVersion, checked, validate_pipeline_version);
37
38const FACT_ID_PREFIX: &str = "m_";
39const KEY_MAX_LEN: usize = 64;
40const PIPELINE_VERSION_LEN: usize = 16;
41
42impl BrainFactId {
43    pub fn generate() -> Self {
44        Self(format!("{FACT_ID_PREFIX}{}", uuid::Uuid::new_v4()))
45    }
46}
47
48fn validate_fact_id(value: &str) -> Result<(), IdValidationError> {
49    let Some(rest) = value.strip_prefix(FACT_ID_PREFIX) else {
50        return Err(IdValidationError::invalid(
51            "BrainFactId",
52            "must start with 'm_'",
53        ));
54    };
55    crate::macros::validate_uuid("BrainFactId", rest)
56}
57
58fn validate_key(name: &'static str, value: &str) -> Result<(), IdValidationError> {
59    if value.is_empty() {
60        return Err(IdValidationError::empty(name));
61    }
62    if value.len() > KEY_MAX_LEN {
63        return Err(IdValidationError::invalid(
64            name,
65            "must be at most 64 characters",
66        ));
67    }
68    if !value.bytes().all(|b| {
69        b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'.' || b == b'-'
70    }) {
71        return Err(IdValidationError::invalid(
72            name,
73            "must contain only lowercase letters, digits, '_', '.' and '-'",
74        ));
75    }
76    Ok(())
77}
78
79fn validate_pipeline_version(value: &str) -> Result<(), IdValidationError> {
80    if value.len() != PIPELINE_VERSION_LEN
81        || !value
82            .bytes()
83            .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
84    {
85        return Err(IdValidationError::invalid(
86            "PipelineVersion",
87            "must be 16 lowercase hex characters",
88        ));
89    }
90    Ok(())
91}