From 2369f3d5415d0e7eb79edc147b82a0072f0b94c9 Mon Sep 17 00:00:00 2001 From: Max Haarhaus Date: Tue, 14 Jul 2026 18:07:55 -0400 Subject: [PATCH 01/10] refactor(adapters): stage the registry for layered descriptor sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Carve descriptor provenance into a new descriptor::layers module (Layer, DescriptorSource, embedded_sources) and rebuild the registry on top of it: entries now record their contributing sources, build_registry takes owned DescriptorSource values and returns a Result (duplicate labels error instead of panicking), and the static becomes a OnceLock whose lazy fallback loads the embedded built-ins only — the seam #136's user-supplied layers feed into next. Co-Authored-By: Claude Fable 5 --- src/adapters/descriptor.rs | 1 + src/adapters/descriptor/layers.rs | 50 ++++++++++++ src/adapters/descriptor_adapter.rs | 1 + src/adapters/registry.rs | 123 +++++++++++++++++++++-------- 4 files changed, 144 insertions(+), 31 deletions(-) create mode 100644 src/adapters/descriptor/layers.rs diff --git a/src/adapters/descriptor.rs b/src/adapters/descriptor.rs index e249122..1f073e6 100644 --- a/src/adapters/descriptor.rs +++ b/src/adapters/descriptor.rs @@ -19,6 +19,7 @@ use crate::validation::{SchemaName, ValidationError, validate_against_schema}; use super::capabilities::{GuardEngine, ShadowPreflight, SlugCapability, TranscriptParser}; +pub mod layers; mod validation; /// The three built-in harness descriptors, embedded like the schemas: a diff --git a/src/adapters/descriptor/layers.rs b/src/adapters/descriptor/layers.rs new file mode 100644 index 0000000..8209252 --- /dev/null +++ b/src/adapters/descriptor/layers.rs @@ -0,0 +1,50 @@ +//! Descriptor source layers: where a descriptor's TOML text came from. +//! +//! Every descriptor source carries its layer and display path so registry +//! entries can report provenance (`harness list`/`show`) and error messages +//! can name the contributing file. + +use super::EMBEDDED_DESCRIPTORS; + +/// The layer a descriptor source belongs to, in precedence order (later +/// layers override earlier ones field-by-field). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Layer { + /// A built-in descriptor bundled into the binary (`harnesses/*.toml`). + Embedded, +} + +/// One descriptor source: its layer, a display path for error messages and +/// provenance, and the raw TOML text. +#[derive(Debug, Clone)] +pub struct DescriptorSource { + pub layer: Layer, + pub path: String, + pub toml_src: String, +} + +/// The bundled built-in descriptors as the registry's base layer. +pub fn embedded_sources() -> Vec { + EMBEDDED_DESCRIPTORS + .iter() + .map(|(path, toml_src)| DescriptorSource { + layer: Layer::Embedded, + path: (*path).to_string(), + toml_src: (*toml_src).to_string(), + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn embedded_sources_cover_every_bundled_descriptor() { + let sources = embedded_sources(); + assert_eq!(sources.len(), EMBEDDED_DESCRIPTORS.len()); + assert!(sources.iter().all(|s| s.layer == Layer::Embedded)); + assert_eq!(sources[0].path, "harnesses/claude-code.toml"); + assert!(!sources[0].toml_src.is_empty()); + } +} diff --git a/src/adapters/descriptor_adapter.rs b/src/adapters/descriptor_adapter.rs index ae4c8ee..4321296 100644 --- a/src/adapters/descriptor_adapter.rs +++ b/src/adapters/descriptor_adapter.rs @@ -23,6 +23,7 @@ use super::skill_shadow::PluginShadowReport; use super::skills_block::{DEFAULT_HEADER, DEFAULT_ITEM, render_skills_block}; /// A [`HarnessAdapter`] backed by a validated [`HarnessDescriptor`]. +#[derive(Debug)] pub struct DescriptorAdapter { descriptor: HarnessDescriptor, /// Compiled `staging.stage_name_pattern`; validated to compile at load diff --git a/src/adapters/registry.rs b/src/adapters/registry.rs index 66a3412..08c7ee4 100644 --- a/src/adapters/registry.rs +++ b/src/adapters/registry.rs @@ -7,53 +7,87 @@ //! and the artifact `Deserialize`), [`Harness::known`] enumerates the entries, //! and [`adapter_for`] serves each handle's [`HarnessAdapter`]. -use std::sync::LazyLock; +use std::sync::{LazyLock, OnceLock}; use crate::core::Harness; -use super::descriptor::{EMBEDDED_DESCRIPTORS, load_descriptor}; +use super::descriptor::layers::{DescriptorSource, Layer, embedded_sources}; +use super::descriptor::{DescriptorError, load_descriptor}; use super::descriptor_adapter::DescriptorAdapter; use super::harness::{HarnessAdapter, ToolVocabulary}; -/// One registry entry: a harness identity (the descriptor's `label`) and its -/// descriptor-backed adapter. +/// One registry entry: a harness identity (the descriptor's `label`), the +/// descriptor sources that contributed to it (for `harness list`/`show` +/// provenance), and its descriptor-backed adapter. +#[derive(Debug)] struct RegistryEntry { label: &'static str, + sources: Vec<(Layer, String)>, adapter: DescriptorAdapter, } -/// Built on first use. The embedded descriptors are bundled and known-valid, -/// so a load failure here is a programmer error (a bad descriptor edit) and -/// panics — mirroring the bundled-schema panics in `validation::schema` — -/// with the descriptor's own actionable message. -static REGISTRY: LazyLock> = - LazyLock::new(|| build_registry(EMBEDDED_DESCRIPTORS)); +/// Set once by `init_registry` (layered sources), or lazily to the embedded +/// built-ins on first pre-init access. The lazy fallback keeps unit tests +/// hermetic: they never call `init_registry`, so user-supplied descriptor +/// layers can't leak into them. +static REGISTRY: OnceLock> = OnceLock::new(); + +fn registry() -> &'static Vec { + REGISTRY.get_or_init(|| { + // The embedded descriptors are bundled and known-valid, so a failure + // here is a programmer error (a bad descriptor edit) and panics — + // mirroring the bundled-schema panics in `validation::schema` — with + // the descriptor's own actionable message. + build_registry(embedded_sources()) + .unwrap_or_else(|e| panic!("bundled harness descriptor is invalid: {e}")) + }) +} + +/// A descriptor source set that cannot form a registry. +#[derive(Debug, thiserror::Error)] +enum RegistryBuildError { + #[error(transparent)] + Descriptor(#[from] DescriptorError), + #[error("duplicate harness label {label:?}: {first} and {second}")] + DuplicateLabel { + label: String, + first: String, + second: String, + }, +} /// Load descriptor sources into registry entries, keyed by each descriptor's /// `label`. The label is the harness identity — `--harness