agent-migrate-core 0.6.1

Core models and transactional filesystem engine for agent configuration migration
Documentation
use std::collections::BTreeMap;

use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::bundle::BundleError;
use crate::{AgentKind, BundleRelativePath, PathMapping, Scope, SecretRef};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SchemaVersion {
    major: u16,
    minor: u16,
}

impl SchemaVersion {
    pub const V1: Self = Self { major: 1, minor: 0 };

    /// Creates a supported Bundle schema version.
    ///
    /// # Errors
    ///
    /// Returns [`BundleError::UnsupportedMajor`] when `major` is not supported.
    pub fn new(major: u16, minor: u16) -> Result<Self, BundleError> {
        if major != Self::V1.major {
            return Err(BundleError::UnsupportedMajor(major));
        }
        Ok(Self { major, minor })
    }

    #[must_use]
    pub const fn major(self) -> u16 {
        self.major
    }

    #[must_use]
    pub const fn minor(self) -> u16 {
        self.minor
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct BundleId(Uuid);

impl BundleId {
    #[must_use]
    pub const fn from_uuid(value: Uuid) -> Self {
        Self(value)
    }

    #[must_use]
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }
}

impl Default for BundleId {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FileKind {
    Skill,
    Mcp,
    Instructions,
    Command,
    Agent,
    Hook,
    Settings,
    Passthrough,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileRecord {
    pub kind: FileKind,
    pub mode: u32,
    pub sha256: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceDescriptor {
    pub agent: AgentKind,
    pub agent_version: String,
    pub scope: Scope,
    pub platform: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceEnvironment {
    agent_version: String,
    scope: Scope,
    platform: String,
}

impl SourceEnvironment {
    #[must_use]
    pub fn new(
        agent_version: impl Into<String>,
        scope: Scope,
        platform: impl Into<String>,
    ) -> Self {
        Self {
            agent_version: agent_version.into(),
            scope,
            platform: platform.into(),
        }
    }
}

impl SourceDescriptor {
    #[must_use]
    pub fn new(agent: AgentKind, environment: SourceEnvironment) -> Self {
        Self {
            agent,
            agent_version: environment.agent_version,
            scope: environment.scope,
            platform: environment.platform,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Manifest {
    pub schema: SchemaVersion,
    pub bundle_id: BundleId,
    pub created_at: Timestamp,
    pub source: SourceDescriptor,
    pub files: BTreeMap<BundleRelativePath, FileRecord>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub path_mappings: Vec<PathMapping>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub secret_references: Vec<SecretRef>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ManifestIdentity {
    schema: SchemaVersion,
    bundle_id: BundleId,
    created_at: Timestamp,
}

impl ManifestIdentity {
    #[must_use]
    pub const fn new(schema: SchemaVersion, bundle_id: BundleId, created_at: Timestamp) -> Self {
        Self {
            schema,
            bundle_id,
            created_at,
        }
    }
}

impl Manifest {
    #[must_use]
    pub fn new(identity: ManifestIdentity, source: SourceDescriptor) -> Self {
        Self {
            schema: identity.schema,
            bundle_id: identity.bundle_id,
            created_at: identity.created_at,
            source,
            files: BTreeMap::new(),
            path_mappings: Vec::new(),
            secret_references: Vec::new(),
        }
    }

    #[must_use]
    pub fn with_path_mappings(mut self, path_mappings: Vec<PathMapping>) -> Self {
        self.path_mappings = path_mappings;
        self
    }

    #[must_use]
    pub fn with_secret_references(mut self, secret_references: Vec<SecretRef>) -> Self {
        self.secret_references = secret_references;
        self
    }
}