1use std::sync::OnceLock;
5
6use murmur_artifact::{parse_wit_levels, WitLevel};
7
8pub const SERVED_WIT_PACKAGES: &[(&str, &str)] = &[
21 ("murmur:artifact-manager", "0.1.0"),
22 ("murmur:capsule", "0.1.0"),
23 ("murmur:conversation", "0.2.0"),
24 ("murmur:driver", "0.6.0"),
25 ("murmur:hook", "0.9.0"),
26 ("murmur:host", "0.1.0"),
27 ("murmur:message", "0.1.0"),
28 ("murmur:process", "0.1.0"),
29 ("murmur:runtime", "0.4.0"),
30 ("murmur:runtime-guest", "0.1.0"),
31 ("murmur:shell", "0.1.0"),
32 ("murmur:stream", "0.1.0"),
33 ("murmur:task", "0.1.0"),
34 ("murmur:task-io", "0.1.0"),
35 ("murmur:tool", "0.2.0"),
36 ("murmur:tool-registry", "0.2.0"),
37];
38
39pub const WIT_LEVELS_FILE: &str = "crates/capsule-runtime/wit/levels.txt";
41
42pub fn wit_levels() -> &'static [WitLevel] {
47 static LEVELS: OnceLock<Vec<WitLevel>> = OnceLock::new();
48 LEVELS.get_or_init(|| {
49 parse_wit_levels(include_str!("../wit/levels.txt"))
50 .unwrap_or_else(|error| panic!("{WIT_LEVELS_FILE} is not a valid level table: {error}"))
51 })
52}
53
54pub fn served_wit_level() -> &'static WitLevel {
56 wit_levels()
57 .last()
58 .expect("parse_wit_levels refuses a table with no level")
59}
60
61#[cfg(test)]
62mod tests {
63 use std::collections::BTreeSet;
64 use std::path::Path;
65
66 use super::{served_wit_level, wit_levels, SERVED_WIT_PACKAGES, WIT_LEVELS_FILE};
67
68 fn served_set() -> BTreeSet<(String, String)> {
69 SERVED_WIT_PACKAGES
70 .iter()
71 .map(|(package, version)| (package.to_string(), version.to_string()))
72 .collect()
73 }
74
75 #[test]
79 fn wit_versions_table_matches_the_wit_tree() {
80 let declared = murmur_artifact::wit_package_declarations(Path::new(concat!(
81 env!("CARGO_MANIFEST_DIR"),
82 "/wit"
83 )))
84 .unwrap();
85 assert!(!declared.is_empty(), "no murmur package declarations found");
86
87 let mut packages: Vec<&str> = SERVED_WIT_PACKAGES.iter().map(|(p, _)| *p).collect();
88 packages.sort_unstable();
89 packages.dedup();
90 assert_eq!(
91 packages.len(),
92 SERVED_WIT_PACKAGES.len(),
93 "a package appears twice in SERVED_WIT_PACKAGES"
94 );
95
96 assert_eq!(served_set(), declared);
97 }
98
99 #[test]
102 fn wit_versions_table_covers_every_resolved_interface() {
103 let resolved = [
104 crate::hooks::LIFECYCLE_IFACE,
105 crate::process_driver::PROCESS_DRIVER_IFACE,
106 crate::runtime::WIT_CAPSULE_IFACE_VERSIONED,
107 crate::runtime::WIT_TOOL_IFACE_VERSIONED,
108 crate::runtime::WIT_TOOL_REGISTRY_IFACE,
109 crate::runtime::WIT_STREAM_EVENTS_IFACE,
110 crate::runtime::WIT_TASK_IFACE,
111 crate::conversation_import::CONVERSATION_IFACE_VERSIONED,
112 crate::inference_import::INFERENCE_IFACE_VERSIONED,
113 crate::task_io_import::TASK_IO_IFACE_VERSIONED,
114 crate::tokens_import::TOKENS_IFACE_VERSIONED,
115 crate::session_process::PROCESS_IFACE,
116 ];
117 let served = served_set();
118 for interface in resolved {
119 let (package, rest) = interface
120 .split_once('/')
121 .unwrap_or_else(|| panic!("{interface} names no interface"));
122 let (_, version) = rest
123 .rsplit_once('@')
124 .unwrap_or_else(|| panic!("{interface} carries no version"));
125 assert!(
126 served.contains(&(package.to_string(), version.to_string())),
127 "{interface} is not at a version SERVED_WIT_PACKAGES lists for {package}"
128 );
129 }
130 }
131
132 #[test]
136 fn wit_versions_every_patch_field_is_zero() {
137 for (package, version) in SERVED_WIT_PACKAGES {
138 let patch = version
139 .split('.')
140 .nth(2)
141 .unwrap_or_else(|| panic!("{package}@{version} has no patch field"));
142 assert_eq!(patch, "0", "{package}@{version} has a non-zero patch field");
143 }
144 }
145
146 #[test]
147 fn wit_levels_file_parses() {
148 murmur_artifact::parse_wit_levels(include_str!("../wit/levels.txt"))
149 .unwrap_or_else(|error| panic!("{WIT_LEVELS_FILE}: {error}"));
150 assert!(!wit_levels().is_empty());
151 }
152
153 #[test]
154 fn wit_levels_served_level_is_level_6() {
155 assert_eq!(served_wit_level().level, 6);
156 }
157
158 #[test]
162 fn wit_levels_level_6_moves_only_the_driver_package() {
163 use murmur_artifact::{version_fit, FitSubject, VersionFit, WitContracts};
164 let levels = wit_levels();
165 let (five, six) = (&levels[4], &levels[5]);
166 assert_eq!(five.packages.len(), six.packages.len());
167 let moved: Vec<_> = five
168 .packages
169 .iter()
170 .zip(&six.packages)
171 .filter(|(before, after)| before != after)
172 .collect();
173 let driver = |version: &str| ("murmur:driver".to_string(), version.to_string());
174 assert_eq!(moved, [(&driver("0.5.0"), &driver("0.6.0"))]);
175
176 let contracts = |exports: &[&str], imports: &[&str]| WitContracts {
177 exports: exports.iter().map(|e| e.to_string()).collect(),
178 imports: imports.iter().map(|i| i.to_string()).collect(),
179 };
180 let fits = |contracts: WitContracts, level| {
181 version_fit(&FitSubject::component(contracts), level) == VersionFit::Fits
182 };
183 let driver_at =
184 |version: &str| contracts(&[&format!("murmur:driver/process@{version}")], &[]);
185 assert!(fits(driver_at("0.5.0"), five));
186 assert!(!fits(driver_at("0.5.0"), six));
187 assert!(fits(driver_at("0.6.0"), six));
188 }
189
190 #[test]
194 fn wit_levels_level_5_moves_only_the_driver_package() {
195 use murmur_artifact::{version_fit, FitSubject, VersionFit, WitContracts};
196 let levels = wit_levels();
197 let (four, five) = (&levels[3], &levels[4]);
198 assert_eq!(four.packages.len(), five.packages.len());
199 let moved: Vec<_> = four
200 .packages
201 .iter()
202 .zip(&five.packages)
203 .filter(|(before, after)| before != after)
204 .collect();
205 let driver = |version: &str| ("murmur:driver".to_string(), version.to_string());
206 assert_eq!(moved, [(&driver("0.4.0"), &driver("0.5.0"))]);
207
208 let contracts = |exports: &[&str], imports: &[&str]| WitContracts {
209 exports: exports.iter().map(|e| e.to_string()).collect(),
210 imports: imports.iter().map(|i| i.to_string()).collect(),
211 };
212 let fits = |contracts: WitContracts, level| {
213 version_fit(&FitSubject::component(contracts), level) == VersionFit::Fits
214 };
215 let tool = || {
216 contracts(
217 &["murmur:tool/run@0.2.0"],
218 &["murmur:stream/events@0.1.0", "murmur:process/process@0.1.0"],
219 )
220 };
221 assert!(fits(tool(), four));
222 assert!(fits(tool(), five));
223 let driver_at =
224 |version: &str| contracts(&[&format!("murmur:driver/process@{version}")], &[]);
225 assert!(fits(driver_at("0.4.0"), four));
226 assert!(!fits(driver_at("0.4.0"), five));
227 assert!(fits(driver_at("0.5.0"), five));
228 }
229
230 #[test]
233 fn wit_levels_level_4_only_adds_the_process_package() {
234 use murmur_artifact::{version_fit, FitSubject, VersionFit, WitContracts};
235 let levels = wit_levels();
236 let (three, four) = (&levels[2], &levels[3]);
237 let added: Vec<_> = four
238 .packages
239 .iter()
240 .filter(|package| !three.packages.contains(package))
241 .collect();
242 assert_eq!(
243 added,
244 [&("murmur:process".to_string(), "0.1.0".to_string())]
245 );
246 assert_eq!(four.packages.len(), three.packages.len() + 1);
247
248 let contracts = |imports: &[&str]| WitContracts {
249 exports: vec!["murmur:tool/run@0.2.0".to_string()],
250 imports: imports.iter().map(|i| i.to_string()).collect(),
251 };
252 let fits = |contracts: WitContracts, level| {
253 version_fit(&FitSubject::component(contracts), level) == VersionFit::Fits
254 };
255 let tool = || contracts(&["murmur:stream/events@0.1.0", "murmur:task/task@0.1.0"]);
256 assert!(fits(tool(), three));
257 assert!(fits(tool(), four));
258 let process_tool = || contracts(&["murmur:process/process@0.1.0"]);
259 assert!(!fits(process_tool(), three));
260 assert!(fits(process_tool(), four));
261 }
262
263 #[test]
267 fn wit_levels_level_3_moves_the_tool_packages() {
268 use murmur_artifact::{version_fit, FitSubject, VersionFit, WitContracts};
269 let levels = wit_levels();
270 let (two, three) = (&levels[1], &levels[2]);
271 let moved: Vec<_> = two
272 .packages
273 .iter()
274 .zip(&three.packages)
275 .filter(|(before, after)| before != after)
276 .collect();
277 let package = |name: &str, version: &str| (name.to_string(), version.to_string());
278 assert_eq!(
279 moved,
280 [
281 (
282 &package("murmur:tool", "0.1.0"),
283 &package("murmur:tool", "0.2.0")
284 ),
285 (
286 &package("murmur:tool-registry", "0.1.0"),
287 &package("murmur:tool-registry", "0.2.0")
288 ),
289 ]
290 );
291
292 let contracts = |exports: &[&str], imports: &[&str]| WitContracts {
293 exports: exports.iter().map(|e| e.to_string()).collect(),
294 imports: imports.iter().map(|i| i.to_string()).collect(),
295 };
296 let fits = |contracts: WitContracts, level| {
297 version_fit(&FitSubject::component(contracts), level) == VersionFit::Fits
298 };
299 let old_tool = || contracts(&["murmur:tool/run@0.1.0"], &["murmur:stream/events@0.1.0"]);
300 assert!(fits(old_tool(), two));
301 assert!(!fits(old_tool(), three));
302 let tool = contracts(&["murmur:tool/run@0.2.0"], &["murmur:stream/events@0.1.0"]);
303 assert!(fits(tool, three));
304 let hook = || {
305 contracts(
306 &["murmur:hook/lifecycle@0.9.0"],
307 &["murmur:task-io/read@0.1.0"],
308 )
309 };
310 assert!(fits(hook(), two));
311 assert!(fits(hook(), three));
312 }
313
314 #[test]
317 fn wit_levels_level_2_moves_only_the_driver_package() {
318 use murmur_artifact::{version_fit, FitSubject, VersionFit, WitContracts};
319 let levels = wit_levels();
320 let (one, two) = (&levels[0], &levels[1]);
321 let moved: Vec<_> = one
322 .packages
323 .iter()
324 .zip(&two.packages)
325 .filter(|(before, after)| before != after)
326 .collect();
327 let driver = |version: &str| ("murmur:driver".to_string(), version.to_string());
328 assert_eq!(moved, [(&driver("0.3.0"), &driver("0.4.0"))]);
329
330 let contracts = |exports: &[&str], imports: &[&str]| WitContracts {
331 exports: exports.iter().map(|e| e.to_string()).collect(),
332 imports: imports.iter().map(|i| i.to_string()).collect(),
333 };
334 let fits = |contracts: WitContracts, level| {
335 version_fit(&FitSubject::component(contracts), level) == VersionFit::Fits
336 };
337 let hook = || {
338 contracts(
339 &["murmur:hook/lifecycle@0.9.0"],
340 &[
341 "murmur:runtime/inference@0.4.0",
342 "murmur:task-io/read@0.1.0",
343 ],
344 )
345 };
346 let tool = || contracts(&["murmur:tool/run@0.1.0"], &["murmur:stream/events@0.1.0"]);
347 for (what, built) in [("hook", hook()), ("tool", tool())] {
348 assert!(fits(built.clone(), one), "{what} at level 1");
349 assert!(fits(built, two), "{what} at level 2");
350 }
351 let old_driver = || contracts(&["murmur:driver/process@0.3.0"], &[]);
352 assert!(fits(old_driver(), one));
353 assert!(!fits(old_driver(), two));
354 }
355
356 #[test]
359 fn wit_levels_newest_level_is_the_served_table() {
360 let newest = served_wit_level();
361 let level: BTreeSet<(String, String)> = newest.packages.iter().cloned().collect();
362 let served = served_set();
363 if level == served {
364 return;
365 }
366 let packages: BTreeSet<&str> = level
367 .iter()
368 .chain(served.iter())
369 .map(|(package, _)| package.as_str())
370 .collect();
371 let differences: Vec<String> = packages
372 .into_iter()
373 .filter_map(|package| {
374 let at_level = newest.version_of(package);
375 let in_table = SERVED_WIT_PACKAGES
376 .iter()
377 .find(|(p, _)| *p == package)
378 .map(|(_, v)| *v);
379 (at_level != in_table).then(|| {
380 format!(
381 " {package}: {} in level {} → {} in SERVED_WIT_PACKAGES",
382 at_level.unwrap_or("absent"),
383 newest.level,
384 in_table.unwrap_or("absent")
385 )
386 })
387 })
388 .collect();
389 let next = newest.level + 1;
390 panic!(
391 "SERVED_WIT_PACKAGES no longer equals WIT level {level} in {WIT_LEVELS_FILE}:\n{}\n\
392 append level {next} to {WIT_LEVELS_FILE}, listing every package at its new version \
393 (one `{next} <package>@<version>` line per package); never edit, renumber or reuse \
394 level {level}",
395 differences.join("\n"),
396 level = newest.level,
397 );
398 }
399}