nmbrs_workload/catalog.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Bundled-workload catalog — SRD-85.
5//!
6//! Workloads embedded into the artifact, discoverable by
7//! catalog name (`cql/keyvalue`, `examples/lfsr`). The catalog
8//! is assembled once at process startup from the manifests each
9//! contributing crate generates at build time (see
10//! `tools/bundle-gen`): the core binary contributes the curated
11//! `workloads/` set and the `examples/` tier; adapter crates
12//! contribute their own `adapters/<a>/workloads/` sets behind
13//! their feature gates, so the catalog is truthful about what
14//! *this* binary can run.
15//!
16//! Visibility tiers separate the two audiences: everything in
17//! the catalog is runnable by name, but only the
18//! [`Tier::Curated`] entries are *listed* by default —
19//! `nmbrs describe workloads` shows the products, not fifty test
20//! fixtures; `--all` (or the `examples` subtopic) reveals the
21//! rest.
22//!
23//! Resolution policy lives with the resolver
24//! (`nmbrs-runtime::runner::resolve_workload`): nearest-first —
25//! the logical filesystem location is favored over the catalog
26//! by default, and a name that resolves both ways picks the
27//! local file with a LOGGED WARNING naming both candidates.
28//! Shadowing is allowed but never silent; `--strict` promotes
29//! the warning to an error.
30
31use std::sync::OnceLock;
32
33/// Visibility tier of a bundled workload.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Tier {
36 /// Curated, real-world workloads — listed by default.
37 /// Required to carry a `description:` (lint-enforced).
38 Curated,
39 /// Teaching examples and coverage workloads — bundled and
40 /// runnable (artifact smoke-testing as-is, anywhere), but
41 /// unlisted unless explicitly requested.
42 Example,
43}
44
45impl Tier {
46 pub fn as_str(self) -> &'static str {
47 match self {
48 Tier::Curated => "curated",
49 Tier::Example => "example",
50 }
51 }
52}
53
54/// One embedded workload.
55#[derive(Debug)]
56pub struct BundledWorkload {
57 /// Catalog name: `<namespace>/<stem>` (no extension), e.g.
58 /// `cql/keyvalue`, `examples/signals/lfsr`. Top-level
59 /// curated entries have no namespace.
60 pub name: &'static str,
61 /// Visibility tier.
62 pub tier: Tier,
63 /// The yaml source, embedded at build time.
64 pub source: &'static str,
65}
66
67static CATALOG: OnceLock<Vec<&'static BundledWorkload>> = OnceLock::new();
68
69/// Install the catalog from the contributing manifests. Called
70/// once at process startup (before any workload resolution);
71/// subsequent calls are ignored — the first installation wins,
72/// which keeps test harnesses that re-enter startup idempotent.
73///
74/// Panics on a duplicate catalog name across sets: two crates
75/// claiming one name is a build/packaging bug, not an operator
76/// condition.
77pub fn install(sets: &[&'static [BundledWorkload]]) {
78 let _ = CATALOG.set({
79 let mut all: Vec<&'static BundledWorkload> = sets.iter().flat_map(|s| s.iter()).collect();
80 all.sort_by_key(|w| w.name);
81 for pair in all.windows(2) {
82 assert_ne!(
83 pair[0].name, pair[1].name,
84 "bundled workload name collision across catalog sets: `{}`",
85 pair[0].name,
86 );
87 }
88 all
89 });
90}
91
92/// Exact-name lookup. No globbing, no fuzzy matching —
93/// `nmbrs describe workloads` is the discovery surface, not the
94/// resolver.
95pub fn lookup(name: &str) -> Option<&'static BundledWorkload> {
96 CATALOG
97 .get()?
98 .binary_search_by_key(&name, |w| w.name)
99 .ok()
100 .map(|idx| CATALOG.get().unwrap()[idx])
101}
102
103/// All catalog entries in name order. Empty when no catalog was
104/// installed (library consumers without the nmbrs binary's
105/// startup hook).
106pub fn iter() -> impl Iterator<Item = &'static BundledWorkload> {
107 CATALOG
108 .get()
109 .map(|v| v.as_slice())
110 .unwrap_or(&[])
111 .iter()
112 .copied()
113}
114
115/// Entries of one tier, in name order.
116pub fn iter_tier(tier: Tier) -> impl Iterator<Item = &'static BundledWorkload> {
117 iter().filter(move |w| w.tier == tier)
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 // The install-once global makes classic unit isolation
125 // awkward; these tests exercise the pure parts and a single
126 // install path. (E2e coverage drives the real assembled
127 // catalog through the nmbrs binary.)
128 static SET_A: &[BundledWorkload] = &[
129 BundledWorkload {
130 name: "alpha",
131 tier: Tier::Curated,
132 source: "description: a\n",
133 },
134 BundledWorkload {
135 name: "examples/beta",
136 tier: Tier::Example,
137 source: "# b\n",
138 },
139 ];
140
141 #[test]
142 fn install_lookup_and_tier_filter() {
143 install(&[SET_A]);
144 // Idempotent re-install is a no-op.
145 install(&[]);
146 assert!(lookup("alpha").is_some());
147 assert!(lookup("examples/beta").is_some());
148 assert!(lookup("nope").is_none());
149 let curated: Vec<_> = iter_tier(Tier::Curated).map(|w| w.name).collect();
150 assert_eq!(curated, vec!["alpha"]);
151 let all: Vec<_> = iter().map(|w| w.name).collect();
152 assert_eq!(all, vec!["alpha", "examples/beta"]);
153 }
154}