//! Agent templates (global IDE store) and their monotonic versioning. use serde::{Deserialize, Serialize}; use crate::error::DomainError; use crate::ids::{ProfileId, TemplateId}; use crate::markdown::MarkdownDoc; /// Monotonically increasing template version. /// /// Invariant (enforced via [`TemplateVersion::next`]): a version only ever /// increases by one when the template content changes (ARCHITECTURE ยง8.1). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] pub struct TemplateVersion(pub u64); impl TemplateVersion { /// The initial version assigned to a freshly created template. pub const INITIAL: Self = Self(1); /// Returns the next version (current + 1). #[must_use] pub const fn next(self) -> Self { Self(self.0 + 1) } /// Returns the raw version number. #[must_use] pub const fn get(self) -> u64 { self.0 } } impl Default for TemplateVersion { fn default() -> Self { Self::INITIAL } } /// A reusable agent template stored in the global IDE store. /// /// Invariants: /// - `name` non-empty, /// - `version` is monotonic; bumping is done via [`AgentTemplate::with_updated_content`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AgentTemplate { /// Stable identifier. pub id: TemplateId, /// Display name. pub name: String, /// Markdown content. pub content_md: MarkdownDoc, /// Current version. pub version: TemplateVersion, /// Default runtime profile for agents created from this template. pub default_profile_id: ProfileId, } impl AgentTemplate { /// Builds a validated template at [`TemplateVersion::INITIAL`]. /// /// # Errors /// Returns [`DomainError::EmptyField`] if `name` is empty. pub fn new( id: TemplateId, name: impl Into, content_md: MarkdownDoc, default_profile_id: ProfileId, ) -> Result { let name = name.into(); crate::validation::non_empty(&name, "template.name")?; Ok(Self { id, name, content_md, version: TemplateVersion::INITIAL, default_profile_id, }) } /// Returns a copy of this template with new content and the version bumped /// by one, preserving the monotonic-version invariant. #[must_use] pub fn with_updated_content(&self, content_md: MarkdownDoc) -> Self { Self { content_md, version: self.version.next(), ..self.clone() } } }