1use std::collections::BTreeMap;
2
3use jiff::Timestamp;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::bundle::BundleError;
8use crate::{AgentKind, BundleRelativePath, PathMapping, Scope, SecretRef};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub struct SchemaVersion {
12 major: u16,
13 minor: u16,
14}
15
16impl SchemaVersion {
17 pub const V1: Self = Self { major: 1, minor: 0 };
18
19 pub fn new(major: u16, minor: u16) -> Result<Self, BundleError> {
25 if major != Self::V1.major {
26 return Err(BundleError::UnsupportedMajor(major));
27 }
28 Ok(Self { major, minor })
29 }
30
31 #[must_use]
32 pub const fn major(self) -> u16 {
33 self.major
34 }
35
36 #[must_use]
37 pub const fn minor(self) -> u16 {
38 self.minor
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
43#[serde(transparent)]
44pub struct BundleId(Uuid);
45
46impl BundleId {
47 #[must_use]
48 pub const fn from_uuid(value: Uuid) -> Self {
49 Self(value)
50 }
51
52 #[must_use]
53 pub fn new() -> Self {
54 Self(Uuid::new_v4())
55 }
56}
57
58impl Default for BundleId {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum FileKind {
67 Skill,
68 Mcp,
69 Instructions,
70 Command,
71 Agent,
72 Hook,
73 Settings,
74 Passthrough,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct FileRecord {
79 pub kind: FileKind,
80 pub mode: u32,
81 pub sha256: String,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub struct SourceDescriptor {
86 pub agent: AgentKind,
87 pub agent_version: String,
88 pub scope: Scope,
89 pub platform: String,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct SourceEnvironment {
94 agent_version: String,
95 scope: Scope,
96 platform: String,
97}
98
99impl SourceEnvironment {
100 #[must_use]
101 pub fn new(
102 agent_version: impl Into<String>,
103 scope: Scope,
104 platform: impl Into<String>,
105 ) -> Self {
106 Self {
107 agent_version: agent_version.into(),
108 scope,
109 platform: platform.into(),
110 }
111 }
112}
113
114impl SourceDescriptor {
115 #[must_use]
116 pub fn new(agent: AgentKind, environment: SourceEnvironment) -> Self {
117 Self {
118 agent,
119 agent_version: environment.agent_version,
120 scope: environment.scope,
121 platform: environment.platform,
122 }
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub struct Manifest {
128 pub schema: SchemaVersion,
129 pub bundle_id: BundleId,
130 pub created_at: Timestamp,
131 pub source: SourceDescriptor,
132 pub files: BTreeMap<BundleRelativePath, FileRecord>,
133 #[serde(default, skip_serializing_if = "Vec::is_empty")]
134 pub path_mappings: Vec<PathMapping>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
136 pub secret_references: Vec<SecretRef>,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct ManifestIdentity {
141 schema: SchemaVersion,
142 bundle_id: BundleId,
143 created_at: Timestamp,
144}
145
146impl ManifestIdentity {
147 #[must_use]
148 pub const fn new(schema: SchemaVersion, bundle_id: BundleId, created_at: Timestamp) -> Self {
149 Self {
150 schema,
151 bundle_id,
152 created_at,
153 }
154 }
155}
156
157impl Manifest {
158 #[must_use]
159 pub fn new(identity: ManifestIdentity, source: SourceDescriptor) -> Self {
160 Self {
161 schema: identity.schema,
162 bundle_id: identity.bundle_id,
163 created_at: identity.created_at,
164 source,
165 files: BTreeMap::new(),
166 path_mappings: Vec::new(),
167 secret_references: Vec::new(),
168 }
169 }
170
171 #[must_use]
172 pub fn with_path_mappings(mut self, path_mappings: Vec<PathMapping>) -> Self {
173 self.path_mappings = path_mappings;
174 self
175 }
176
177 #[must_use]
178 pub fn with_secret_references(mut self, secret_references: Vec<SecretRef>) -> Self {
179 self.secret_references = secret_references;
180 self
181 }
182}