Skip to main content

trait_kit/kit/
kit.rs

1// Copyright (c) 2026 Kirky.X
2// SPDX-License-Identifier: MIT
3//! Kit — the capability and configuration management center.
4//!
5//! Uses typestate pattern: `Kit` (unbuilt) → `Kit<Ready>` (after `build()`).
6
7use std::any::{Any, TypeId};
8use std::cell::RefCell;
9use std::collections::HashMap;
10#[cfg(feature = "reload")]
11use std::rc::Rc;
12use std::sync::OnceLock;
13
14use crate::core::{AutoBuilder, BuildFn};
15use crate::error::TraitKitError;
16use crate::i18n::tr;
17
18#[cfg(feature = "encryption")]
19use super::EncryptedBlob;
20use super::TypeMap;
21use super::{DependencyGraph, GraphError, ModuleEntry};
22
23#[cfg(feature = "lifecycle")]
24type ShutdownCallback = Box<dyn Fn(&TypeMap)>;
25#[cfg(feature = "lifecycle")]
26type ReadyCallback = Box<dyn Fn(&Kit<Ready>) -> Result<(), TraitKitError>>;
27#[cfg(feature = "health")]
28type HealthCheckerFn = Box<dyn Fn(&TypeMap) -> crate::core::health::HealthStatus>;
29#[cfg(feature = "observer")]
30type ObserverRef = std::sync::Arc<dyn crate::core::observer::BuildObserver>;
31#[cfg(feature = "decorator")]
32type DecoratorFn = Box<dyn Fn(Box<dyn Any>) -> Box<dyn Any>>;
33
34/// HKDF key-derivation version label bound into every per-field key.
35/// Bumping this rotates all encrypted configs without changing master keys.
36#[cfg(feature = "encryption")]
37const KEY_DERIVATION_VERSION: &str = "v1";
38
39/// Derive a per-field encryption key, mapping HKDF failures to `TraitKitError`.
40#[cfg(feature = "encryption")]
41fn derive_kit_field_key(
42    master_key: &[u8],
43    path: &'static str,
44    context: &'static str,
45) -> Result<[u8; 32], TraitKitError> {
46    super::config::derive_field_key(master_key, path, KEY_DERIVATION_VERSION).map_err(|e| {
47        TraitKitError::BuildFailed {
48            context: context.to_string(),
49            source: Box::new(e),
50        }
51    })
52}
53
54/// Marker type for the unbuilt state.
55pub struct Unbuilt;
56
57/// Marker type for the ready (built) state.
58pub struct Ready;
59
60/// Type alias for reload subscriber callbacks (single-threaded, `!Sync`).
61#[cfg(feature = "reload")]
62type SubscriberMap = RefCell<HashMap<TypeId, Vec<Rc<dyn Fn()>>>>;
63
64/// Type alias for the encrypted config store (single-threaded, `!Sync`).
65#[cfg(feature = "encryption")]
66type EncryptedConfigMap = RefCell<HashMap<TypeId, EncryptedBlob>>;
67
68/// A lazy construction slot: holds a `build_fn` and a `OnceLock` cache cell.
69/// The builder is invoked on first access; the result is cached in the
70/// `OnceLock` for subsequent accesses. After construction, `builder` is
71/// `None` (consumed) and `cell` holds the built capability.
72///
73/// Shared between `Kit` and `Scope` to avoid struct duplication.
74pub(crate) struct LazySlot {
75    pub(crate) builder: Option<BuildFn>,
76    pub(crate) cell: OnceLock<Box<dyn Any>>,
77}
78
79/// The capability and configuration management center.
80pub struct Kit<S = Unbuilt> {
81    builders: RefCell<HashMap<TypeId, BuildFn>>,
82    /// Override map for test injection: `TypeId` of module → pre-built capability.
83    /// Populated by `override_module` / `override_module_strict`; consumed by `build()`.
84    overrides: RefCell<HashMap<TypeId, Box<dyn Any>>>,
85    /// Lazy builders (Unbuilt state): modules registered via `register_lazy`.
86    /// Transferred to `lazy_slots` during `build()`.
87    lazy_builders: RefCell<HashMap<TypeId, BuildFn>>,
88    /// Lazy slots (Ready state): `build_fn` + `OnceLock` cache. Populated by
89    /// `build()` from `lazy_builders`. Consumed by `require()` on first access.
90    lazy_slots: RefCell<HashMap<TypeId, LazySlot>>,
91    /// Multi-binding builders (Unbuilt state): modules registered via
92    /// `register_multi`. Keyed by `TypeId::of::<M::Capability>()` (not the
93    /// module type) so multiple module types with the same capability type
94    /// aggregate into one Vec. Built into `multi_capabilities` during
95    /// `build()` by T011.
96    multi_builders: RefCell<HashMap<TypeId, Vec<BuildFn>>>,
97    /// Multi-binding capabilities (Ready state): built results from
98    /// `multi_builders`. Keyed by `TypeId::of::<M::Capability>()`.
99    /// Populated by `build()`; consumed by `require_all()`.
100    multi_capabilities: RefCell<HashMap<TypeId, Vec<Box<dyn Any>>>>,
101    /// Interface builders (Unbuilt state): modules registered via
102    /// `register_as`. Keyed by `TypeId::of::<M::Interface>()` (not the
103    /// module type) so `resolve::<I>()` retrieves by interface type.
104    /// Built into `capabilities` during `build()` (T015).
105    #[cfg(feature = "interface")]
106    interface_builders: RefCell<HashMap<TypeId, BuildFn>>,
107    graph: DependencyGraph,
108    configs: TypeMap,
109    capabilities: TypeMap,
110    #[cfg(feature = "reload")]
111    subscribers: SubscriberMap,
112    #[cfg(feature = "encryption")]
113    encrypted_configs: EncryptedConfigMap,
114    #[cfg(feature = "confers")]
115    config_snapshots: RefCell<HashMap<TypeId, Box<dyn Any>>>,
116    #[cfg(feature = "toggle")]
117    toggles: RefCell<HashMap<String, bool>>,
118    #[cfg(feature = "lifecycle")]
119    shutdown_callbacks: RefCell<Vec<(TypeId, ShutdownCallback)>>,
120    #[cfg(feature = "lifecycle")]
121    ready_callbacks: RefCell<Vec<(TypeId, ReadyCallback)>>,
122    #[cfg(feature = "health")]
123    health_checkers: RefCell<HashMap<TypeId, (/* module_name */ &'static str, HealthCheckerFn)>>,
124    #[cfg(feature = "observer")]
125    observers: RefCell<Vec<ObserverRef>>,
126    #[cfg(feature = "decorator")]
127    decorators: RefCell<HashMap<TypeId, Vec<DecoratorFn>>>,
128    /// Maps module `TypeId` → capability `TypeId` for decorator lookup in
129    /// `build_eager_modules()` (where only module `TypeId`s from the
130    /// dependency graph are available).
131    #[cfg(feature = "decorator")]
132    decorator_module_to_cap: RefCell<HashMap<TypeId, TypeId>>,
133    _state: std::marker::PhantomData<S>,
134}
135
136impl Kit {
137    /// Create a new empty Kit.
138    #[must_use]
139    pub fn new() -> Self {
140        Kit {
141            builders: RefCell::new(HashMap::new()),
142            overrides: RefCell::new(HashMap::new()),
143            lazy_builders: RefCell::new(HashMap::new()),
144            lazy_slots: RefCell::new(HashMap::new()),
145            multi_builders: RefCell::new(HashMap::new()),
146            multi_capabilities: RefCell::new(HashMap::new()),
147            #[cfg(feature = "interface")]
148            interface_builders: RefCell::new(HashMap::new()),
149            graph: DependencyGraph::new(),
150            configs: TypeMap::new(),
151            capabilities: TypeMap::new(),
152            #[cfg(feature = "reload")]
153            subscribers: RefCell::new(HashMap::new()),
154            #[cfg(feature = "encryption")]
155            encrypted_configs: RefCell::new(HashMap::new()),
156            #[cfg(feature = "confers")]
157            config_snapshots: RefCell::new(HashMap::new()),
158            #[cfg(feature = "toggle")]
159            toggles: RefCell::new(HashMap::new()),
160            #[cfg(feature = "lifecycle")]
161            shutdown_callbacks: RefCell::new(Vec::new()),
162            #[cfg(feature = "lifecycle")]
163            ready_callbacks: RefCell::new(Vec::new()),
164            #[cfg(feature = "health")]
165            health_checkers: RefCell::new(HashMap::new()),
166            #[cfg(feature = "observer")]
167            observers: RefCell::new(Vec::new()),
168            #[cfg(feature = "decorator")]
169            decorators: RefCell::new(HashMap::new()),
170            #[cfg(feature = "decorator")]
171            decorator_module_to_cap: RefCell::new(HashMap::new()),
172            _state: std::marker::PhantomData,
173        }
174    }
175
176    /// Register a module for construction.
177    ///
178    /// # Errors
179    ///
180    /// Returns `TraitKitError::AlreadyRegistered` if a module with the same `TypeId` was already registered.
181    pub fn register<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError> {
182        let entry = ModuleEntry {
183            type_id: TypeId::of::<M>(),
184            name: M::NAME,
185            dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
186        };
187
188        self.graph
189            .add(entry)
190            .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
191
192        let build_fn: BuildFn = Box::new(|kit| {
193            let capability = M::build(kit)
194                .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
195            Ok(Box::new(capability) as Box<dyn Any>)
196        });
197
198        self.builders
199            .borrow_mut()
200            .insert(TypeId::of::<M>(), build_fn);
201        Ok(())
202    }
203
204    /// Register a module for lazy construction.
205    ///
206    /// The module is added to the dependency graph (for validation) but its
207    /// `build_fn` is **not** invoked during `build()`. Instead, the `build_fn`
208    /// is stored in `lazy_builders` and transferred to `Kit<Ready>.lazy_slots`
209    /// during `build()`. The capability is constructed on first `require()`
210    /// call and cached via `OnceLock` for subsequent accesses.
211    ///
212    /// This is useful for modules that are expensive to build or may never
213    /// be needed in a particular run.
214    ///
215    /// # Errors
216    ///
217    /// Returns `TraitKitError::AlreadyRegistered` if the module was already
218    /// registered (via `register` or `register_lazy`).
219    /// Returns `TraitKitError::DependencyMissing` if a dependency is not registered.
220    pub fn register_lazy<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError>
221    where
222        M::Capability: Clone + 'static,
223    {
224        let entry = ModuleEntry {
225            type_id: TypeId::of::<M>(),
226            name: M::NAME,
227            dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
228        };
229
230        self.graph
231            .add(entry)
232            .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
233
234        let build_fn: BuildFn = Box::new(|kit| {
235            let capability = M::build(kit)
236                .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
237            Ok(Box::new(capability) as Box<dyn Any>)
238        });
239
240        self.lazy_builders
241            .borrow_mut()
242            .insert(TypeId::of::<M>(), build_fn);
243        Ok(())
244    }
245
246    /// Register a module for multi-binding construction.
247    ///
248    /// Multiple module types that share the same `M::Capability` type can be
249    /// registered via `register_multi`; their `build_fns` are appended to a
250    /// `Vec` keyed by `TypeId::of::<M::Capability>()` (the capability type,
251    /// not the module type). The Vec preserves registration order.
252    ///
253    /// The module is also added to the dependency graph for validation, so
254    /// `M` must be distinct from any previously registered module (via
255    /// `register`, `register_lazy`, or `register_multi`). Two registrations
256    /// of the same module type `M` will return `AlreadyRegistered`.
257    ///
258    /// During `build()`, all multi-binding builders are invoked and the
259    /// results are stored in `multi_capabilities` (T011). Use `require_all`
260    /// to retrieve the ordered Vec of capabilities.
261    ///
262    /// # Errors
263    ///
264    /// Returns `TraitKitError::AlreadyRegistered` if `M` was already registered
265    /// (via any `register*` method). Dependency validation is deferred to
266    /// `build()` (via `graph.validate()`).
267    pub fn register_multi<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError>
268    where
269        M::Capability: Clone + 'static,
270    {
271        let entry = ModuleEntry {
272            type_id: TypeId::of::<M>(),
273            name: M::NAME,
274            dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
275        };
276
277        self.graph
278            .add(entry)
279            .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
280
281        let build_fn: BuildFn = Box::new(|kit| {
282            let capability = M::build(kit)
283                .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
284            Ok(Box::new(capability) as Box<dyn Any>)
285        });
286
287        // Aggregate by capability type so require_all::<M>() returns all
288        // implementations of the same capability type.
289        let cap_id = TypeId::of::<M::Capability>();
290        self.multi_builders
291            .borrow_mut()
292            .entry(cap_id)
293            .or_default()
294            .push(build_fn);
295        Ok(())
296    }
297
298    /// Register a module for interface-based construction.
299    ///
300    /// Unlike `register`, this method stores the `build_fn` keyed by
301    /// `TypeId::of::<M::Interface>()` (the interface type, not the module
302    /// type). The module's `into_interface` method converts the concrete
303    /// capability into `Arc<M::Interface>` during `build()`, enabling
304    /// type-erased retrieval via `resolve::<I>()`.
305    ///
306    /// Only one implementation per interface type is allowed. For multiple
307    /// implementations of the same capability type, use `register_multi`
308    /// instead.
309    ///
310    /// # Errors
311    ///
312    /// Returns `TraitKitError::AlreadyRegistered` if the interface type was
313    /// already registered via `register_as`, or if the module type `M` was
314    /// already registered via any `register*` method.
315    #[cfg(feature = "interface")]
316    pub fn register_as<M>(&mut self) -> Result<(), TraitKitError>
317    where
318        M: crate::core::InterfaceBuilder,
319    {
320        let interface_id = TypeId::of::<M::Interface>();
321
322        // One implementation per interface type.
323        if self.interface_builders.borrow().contains_key(&interface_id) {
324            return Err(TraitKitError::AlreadyRegistered { module: M::NAME });
325        }
326
327        let entry = ModuleEntry {
328            type_id: TypeId::of::<M>(),
329            name: M::NAME,
330            dependencies: M::dependencies().iter().map(|(n, id)| (*n, *id)).collect(),
331        };
332
333        self.graph
334            .add(entry)
335            .map_err(|name| TraitKitError::AlreadyRegistered { module: name })?;
336
337        let build_fn: BuildFn = Box::new(|kit| {
338            let cap = M::build(kit)
339                .map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
340            let iface: std::sync::Arc<M::Interface> = M::into_interface(cap);
341            Ok(Box::new(iface) as Box<dyn Any>)
342        });
343
344        self.interface_builders
345            .borrow_mut()
346            .insert(interface_id, build_fn);
347        Ok(())
348    }
349
350    /// Override a module's capability with a pre-built value, skipping `build_fn`.
351    ///
352    /// Used for test injection: inject a mock capability without running the
353    /// module's build function. Completely skips dependency checking (pure
354    /// unit testing). The module does **not** need to be registered via
355    /// `register()` first — the override is keyed by `TypeId::of::<M>()`.
356    ///
357    /// If `build()` is called later, the override is consumed and the
358    /// original `build_fn` (if any) is never invoked for this module.
359    pub fn override_module<M: AutoBuilder>(&self, capability: M::Capability)
360    where
361        M::Capability: 'static,
362    {
363        self.overrides
364            .borrow_mut()
365            .insert(TypeId::of::<M>(), Box::new(capability));
366    }
367
368    /// Override a module's capability with a pre-built value, but still
369    /// verify that the module's declared dependencies are registered in the
370    /// dependency graph.
371    ///
372    /// Unlike `override_module`, this method requires `&mut self` (exclusive
373    /// access) and checks `M::dependencies()` against the graph. If any
374    /// dependency is not registered, returns `TraitKitError::DependencyMissing`.
375    ///
376    /// The module does **not** need to be registered via `register()` first.
377    /// Only the dependencies must be present.
378    ///
379    /// # Errors
380    ///
381    /// Returns `TraitKitError::DependencyMissing` if any of `M::dependencies()`
382    /// is not registered in the graph.
383    pub fn override_module_strict<M: AutoBuilder>(
384        &mut self,
385        capability: M::Capability,
386    ) -> Result<(), TraitKitError>
387    where
388        M::Capability: 'static,
389    {
390        for (dep_name, dep_id) in M::dependencies() {
391            if self.graph.name_of(*dep_id).is_none() {
392                return Err(TraitKitError::DependencyMissing {
393                    module: M::NAME,
394                    missing: dep_name,
395                });
396            }
397        }
398        self.overrides
399            .borrow_mut()
400            .insert(TypeId::of::<M>(), Box::new(capability));
401        Ok(())
402    }
403
404    /// Set a configuration value.
405    pub fn set_config<C: Clone + 'static>(&self, config: C) {
406        self.configs.insert(config);
407    }
408
409    /// Load a configuration via its `Configurable` implementation and store it.
410    ///
411    /// Requires the `confers` feature. The type must implement `Configurable`,
412    /// typically by delegating to `confers::Config`'s derived `load_sync()`.
413    /// The loaded value overrides any prior `set_config` of the same type.
414    ///
415    /// # Errors
416    ///
417    /// Returns `TraitKitError::BuildFailed` if `Configurable::load` fails.
418    #[cfg(feature = "confers")]
419    pub fn load_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
420        let config = C::load().map_err(|e| TraitKitError::BuildFailed {
421            context: "load_config".into(),
422            source: e,
423        })?;
424        self.set_config(config);
425        Ok(())
426    }
427
428    /// Load a configuration and validate it before storing.
429    ///
430    /// Requires the `confers` feature. Calls `C::load()`, then
431    /// `C::validate()`. The configuration is only stored if validation passes.
432    ///
433    /// # Errors
434    ///
435    /// Returns `TraitKitError::BuildFailed` if loading or validation fails.
436    /// On validation failure, the error source is a `ValidationError` containing
437    /// all failure reasons, and the configuration is not stored.
438    #[cfg(feature = "confers")]
439    pub fn load_and_validate<C>(&self) -> Result<(), TraitKitError>
440    where
441        C: super::Configurable + super::Validatable,
442    {
443        let config = C::load().map_err(|e| TraitKitError::BuildFailed {
444            context: "load_and_validate".into(),
445            source: e,
446        })?;
447        match config.validate() {
448            Ok(()) => {
449                self.set_config(config);
450                Ok(())
451            }
452            Err(errors) => Err(TraitKitError::BuildFailed {
453                context: "load_and_validate".into(),
454                source: Box::new(super::ValidationError { errors }),
455            }),
456        }
457    }
458
459    /// Snapshot the current configuration of type `C`.
460    ///
461    /// Requires the `confers` feature. Clones the current config and stores
462    /// it as a snapshot. Returns `false` if no config of type `C` is present.
463    /// Subsequent snapshots of the same type overwrite the previous one.
464    #[cfg(feature = "confers")]
465    pub fn snapshot_config<C: Clone + 'static>(&self) -> bool {
466        if let Some(config) = self.configs.get_cloned::<C>() {
467            self.config_snapshots
468                .borrow_mut()
469                .insert(TypeId::of::<C>(), Box::new(config));
470            true
471        } else {
472            false
473        }
474    }
475
476    /// Restore a configuration from its snapshot.
477    ///
478    /// Requires the `confers` feature. Clones the snapshot back into the
479    /// configs `TypeMap`, overwriting the current value.
480    ///
481    /// # Errors
482    ///
483    /// Returns `TraitKitError::MissingConfig` if no snapshot exists for `C`.
484    #[cfg(feature = "confers")]
485    pub fn restore_config<C: Clone + 'static>(&self) -> Result<(), TraitKitError> {
486        let snapshots = self.config_snapshots.borrow();
487        let boxed =
488            snapshots
489                .get(&TypeId::of::<C>())
490                .ok_or_else(|| TraitKitError::MissingConfig {
491                    key: format!("{} (snapshot)", std::any::type_name::<C>()),
492                })?;
493        let config =
494            boxed
495                .downcast_ref::<C>()
496                .cloned()
497                .ok_or_else(|| TraitKitError::MissingConfig {
498                    key: format!("{} (snapshot downcast)", std::any::type_name::<C>()),
499                })?;
500        drop(snapshots);
501        self.set_config(config);
502        Ok(())
503    }
504
505    /// Check if a snapshot exists for configuration type `C`.
506    #[cfg(feature = "confers")]
507    pub fn has_snapshot<C: 'static>(&self) -> bool {
508        self.config_snapshots
509            .borrow()
510            .contains_key(&TypeId::of::<C>())
511    }
512
513    /// Load a configuration with variable interpolation.
514    ///
515    /// Requires the `confers` feature. Calls `C::load()`, serializes to JSON,
516    /// replaces `${VAR}` and `${VAR:-default}` patterns in string values using
517    /// the provided `vars` map, then deserializes back and stores the result.
518    ///
519    /// `C` must implement `serde::Serialize` and `serde::de::DeserializeOwned`
520    /// in addition to `Configurable`.
521    ///
522    /// # Errors
523    ///
524    /// Returns `TraitKitError::BuildFailed` if loading, serialization,
525    /// or deserialization fails.
526    #[cfg(feature = "confers")]
527    pub fn load_config_with<C, S: std::hash::BuildHasher>(
528        &self,
529        vars: &std::collections::HashMap<String, String, S>,
530    ) -> Result<(), TraitKitError>
531    where
532        C: super::Configurable + serde::Serialize + serde::de::DeserializeOwned,
533    {
534        let config = C::load().map_err(|e| TraitKitError::BuildFailed {
535            context: "load_config_with".into(),
536            source: e,
537        })?;
538        let mut json_value =
539            serde_json::to_value(&config).map_err(|e| TraitKitError::BuildFailed {
540                context: "load_config_with (serialize)".into(),
541                source: Box::new(e),
542            })?;
543        super::config::interpolate_json_value(&mut json_value, vars);
544        let interpolated: C =
545            serde_json::from_value(json_value).map_err(|e| TraitKitError::BuildFailed {
546                context: "load_config_with (deserialize)".into(),
547                source: Box::new(e),
548            })?;
549        self.set_config(interpolated);
550        Ok(())
551    }
552
553    /// Validate the dependency graph and build all modules in topological order.
554    ///
555    /// After this call, all capabilities are available via `require()`.
556    ///
557    /// # Errors
558    ///
559    /// Returns `TraitKitError::DependencyMissing` if a registered module depends on an unregistered module.
560    /// Returns `TraitKitError::CycleDetected` if a dependency cycle is found.
561    /// Returns `TraitKitError::MissingCapability` if a build function is missing for a sorted module.
562    /// Returns `TraitKitError::BuildFailed` if a module's `build` callback returns an error.
563    pub fn build(self) -> Result<Kit<Ready>, TraitKitError> {
564        let sorted = match self.graph.validate() {
565            Ok(sorted) => sorted,
566            Err(GraphError::DependencyMissing { module, missing }) => {
567                return Err(TraitKitError::DependencyMissing { module, missing });
568            }
569            Err(GraphError::CycleDetected { cycle }) => {
570                return Err(TraitKitError::CycleDetected { cycle });
571            }
572        };
573
574        // Phase 1: Build eager modules (overrides + build_fn in topo order)
575        self.build_eager_modules(&sorted)?;
576
577        // Phase 2: Transfer lazy builders to lazy slots
578        self.transfer_lazy_builders();
579
580        // Phase 3: Build multi-binding modules
581        self.build_multi_bindings()?;
582
583        // Phase 4: Build interface modules
584        #[cfg(feature = "interface")]
585        self.build_interface_modules()?;
586
587        // Extract ready_callbacks before moving self
588        #[cfg(feature = "lifecycle")]
589        let ready_callbacks: Vec<(TypeId, ReadyCallback)> =
590            { self.ready_callbacks.borrow_mut().drain(..).collect() };
591        #[cfg(feature = "lifecycle")]
592        let shutdown_callbacks: Vec<(TypeId, ShutdownCallback)> =
593            { self.shutdown_callbacks.borrow_mut().drain(..).collect() };
594
595        let kit = Kit {
596            builders: self.builders,
597            overrides: self.overrides,
598            lazy_builders: self.lazy_builders,
599            lazy_slots: self.lazy_slots,
600            multi_builders: self.multi_builders,
601            multi_capabilities: self.multi_capabilities,
602            #[cfg(feature = "interface")]
603            interface_builders: self.interface_builders,
604            graph: self.graph,
605            configs: self.configs,
606            capabilities: self.capabilities,
607            #[cfg(feature = "reload")]
608            subscribers: self.subscribers,
609            #[cfg(feature = "encryption")]
610            encrypted_configs: self.encrypted_configs,
611            #[cfg(feature = "confers")]
612            config_snapshots: self.config_snapshots,
613            #[cfg(feature = "toggle")]
614            toggles: self.toggles,
615            #[cfg(feature = "lifecycle")]
616            shutdown_callbacks: RefCell::new(shutdown_callbacks),
617            #[cfg(feature = "lifecycle")]
618            ready_callbacks: RefCell::new(Vec::new()),
619            #[cfg(feature = "health")]
620            health_checkers: self.health_checkers,
621            #[cfg(feature = "observer")]
622            observers: self.observers,
623            #[cfg(feature = "decorator")]
624            decorators: self.decorators,
625            #[cfg(feature = "decorator")]
626            decorator_module_to_cap: self.decorator_module_to_cap,
627            _state: std::marker::PhantomData,
628        };
629
630        // Call lifecycle on_ready callbacks in topological order
631        #[cfg(feature = "lifecycle")]
632        {
633            for (_type_id, callback) in &ready_callbacks {
634                callback(&kit)?;
635            }
636        }
637
638        Ok(kit)
639    }
640
641    /// Phase 1: Build eager modules in topological order.
642    ///
643    /// For each module in the sorted list:
644    /// 1. Check overrides first (skip `build_fn` if override exists)
645    /// 2. Skip lazy-registered modules (deferred to first `require()`)
646    /// 3. Invoke the `build_fn` for regular modules
647    /// 4. Insert remaining unregistered overrides after the loop
648    fn build_eager_modules(&self, sorted: &[TypeId]) -> Result<(), TraitKitError> {
649        for type_id in sorted {
650            let module_name = self.module_name(*type_id);
651
652            // [Override] Priority 1: check overrides map first.
653            if let Some(boxed) = self.overrides.borrow_mut().remove(type_id) {
654                self.capabilities.insert_boxed(*type_id, boxed);
655                continue;
656            }
657
658            // [Lazy] Skip lazy-registered modules — deferred to first require().
659            if self.lazy_builders.borrow().contains_key(type_id) {
660                continue;
661            }
662
663            // [Build] Priority 2: invoke the registered build_fn.
664            let Some(build_fn) = self.builders.borrow_mut().remove(type_id) else {
665                continue;
666            };
667
668            // Observer: notify build start
669            #[cfg(feature = "observer")]
670            {
671                let start_instant = std::time::Instant::now();
672                for obs in self.observers.borrow().iter() {
673                    obs.on_module_start(module_name);
674                }
675
676                match (build_fn)(self) {
677                    Ok(boxed) => {
678                        let elapsed = start_instant.elapsed();
679                        #[cfg(feature = "decorator")]
680                        let boxed = {
681                            let cap_type_id = self
682                                .decorator_module_to_cap
683                                .borrow()
684                                .get(type_id)
685                                .copied()
686                                .unwrap_or(*type_id);
687                            self.apply_decorators(cap_type_id, boxed)
688                        };
689                        self.capabilities.insert_boxed(*type_id, boxed);
690                        for obs in self.observers.borrow().iter() {
691                            obs.on_module_built(module_name, elapsed);
692                        }
693                    }
694                    Err(e) => {
695                        let err = TraitKitError::BuildFailed {
696                            context: module_name.to_string(),
697                            source: e,
698                        };
699                        for obs in self.observers.borrow().iter() {
700                            obs.on_build_error(module_name, &err);
701                        }
702                        return Err(err);
703                    }
704                }
705            }
706
707            #[cfg(not(feature = "observer"))]
708            {
709                match (build_fn)(self) {
710                    Ok(boxed) => {
711                        #[cfg(feature = "decorator")]
712                        let boxed = {
713                            let cap_type_id = self
714                                .decorator_module_to_cap
715                                .borrow()
716                                .get(type_id)
717                                .copied()
718                                .unwrap_or(*type_id);
719                            self.apply_decorators(cap_type_id, boxed)
720                        };
721                        self.capabilities.insert_boxed(*type_id, boxed);
722                    }
723                    Err(e) => {
724                        return Err(TraitKitError::BuildFailed {
725                            context: module_name.to_string(),
726                            source: e,
727                        });
728                    }
729                }
730            }
731        }
732
733        // Handle modules that were overridden but NOT registered.
734        let remaining: Vec<(TypeId, Box<dyn Any>)> = self.overrides.borrow_mut().drain().collect();
735        for (type_id, boxed) in remaining {
736            self.capabilities.insert_boxed(type_id, boxed);
737        }
738        Ok(())
739    }
740
741    /// Phase 2: Transfer lazy builders to lazy slots for first-access construction.
742    fn transfer_lazy_builders(&self) {
743        let lazy: Vec<(TypeId, BuildFn)> = self.lazy_builders.borrow_mut().drain().collect();
744        self.lazy_slots.borrow_mut().reserve(lazy.len());
745        for (type_id, builder) in lazy {
746            self.lazy_slots.borrow_mut().insert(
747                type_id,
748                LazySlot {
749                    builder: Some(builder),
750                    cell: OnceLock::new(),
751                },
752            );
753        }
754    }
755
756    /// Phase 3: Build all multi-binding modules.
757    fn build_multi_bindings(&self) -> Result<(), TraitKitError> {
758        let multi: Vec<(TypeId, Vec<BuildFn>)> = self.multi_builders.borrow_mut().drain().collect();
759        for (cap_id, build_fns) in multi {
760            let mut vec = Vec::with_capacity(build_fns.len());
761            for build_fn in build_fns {
762                let boxed = (build_fn)(self).map_err(|e| TraitKitError::BuildFailed {
763                    context: tr("trait-kit-diag-multi-binding", &[]),
764                    source: e,
765                })?;
766                #[cfg(feature = "decorator")]
767                let boxed = self.apply_decorators(cap_id, boxed);
768                vec.push(boxed);
769            }
770            self.multi_capabilities.borrow_mut().insert(cap_id, vec);
771        }
772        Ok(())
773    }
774
775    /// Phase 4: Build all interface-registered modules.
776    #[cfg(feature = "interface")]
777    fn build_interface_modules(&self) -> Result<(), TraitKitError> {
778        let interfaces: Vec<(TypeId, BuildFn)> =
779            self.interface_builders.borrow_mut().drain().collect();
780        for (interface_id, build_fn) in interfaces {
781            let boxed = (build_fn)(self).map_err(|e| TraitKitError::BuildFailed {
782                context: tr("trait-kit-diag-interface", &[]),
783                source: e,
784            })?;
785            #[cfg(feature = "decorator")]
786            let boxed = self.apply_decorators(interface_id, boxed);
787            self.capabilities.insert_boxed(interface_id, boxed);
788        }
789        Ok(())
790    }
791
792    fn module_name(&self, type_id: TypeId) -> &'static str {
793        self.graph.name_of(type_id).unwrap_or("<unknown>")
794    }
795
796    // ─── Lifecycle ─────────────────────────────────────────────────────
797
798    /// Register lifecycle hooks for a previously registered module.
799    ///
800    /// The module must have been registered via `register::<M>()` first.
801    /// This stores `on_ready` and `on_shutdown` callbacks that are invoked
802    /// during `build()` and `shutdown()` respectively.
803    ///
804    /// Requires the `lifecycle` feature.
805    #[cfg(feature = "lifecycle")]
806    pub fn register_lifecycle<M>(&mut self)
807    where
808        M: crate::core::lifecycle::Lifecycle + 'static,
809        M::Capability: 'static,
810    {
811        // Store shutdown callback
812        let shutdown_cb: ShutdownCallback = Box::new(|caps: &TypeMap| {
813            let type_id = TypeId::of::<M>();
814            if let Some((_guard, cap_ref)) = caps.get_ref_by_type_id::<M::Capability>(type_id) {
815                M::on_shutdown(cap_ref);
816            }
817        });
818        self.shutdown_callbacks
819            .borrow_mut()
820            .push((TypeId::of::<M>(), shutdown_cb));
821
822        // Store ready callback
823        let ready_cb: ReadyCallback = Box::new(|kit: &Kit<Ready>| {
824            M::on_ready(kit).map_err(|e| TraitKitError::LifecycleFailed {
825                context: M::NAME.to_string(),
826                source: Box::new(e),
827            })
828        });
829        self.ready_callbacks
830            .borrow_mut()
831            .push((TypeId::of::<M>(), ready_cb));
832    }
833
834    // ─── Health Check ──────────────────────────────────────────────────
835
836    /// Register a health checker for a previously registered module.
837    ///
838    /// The module must have been registered via `register::<M>()` first.
839    /// Use `health_check::<M>()` or `health_report()` on `Kit<Ready>` to query.
840    ///
841    /// Requires the `health` feature.
842    #[cfg(feature = "health")]
843    pub fn register_health_check<M>(&mut self)
844    where
845        M: crate::core::health::HealthCheck + 'static,
846        M::Capability: 'static,
847    {
848        let checker: HealthCheckerFn = Box::new(|caps: &TypeMap| {
849            let type_id = TypeId::of::<M>();
850            match caps.get_ref_by_type_id::<M::Capability>(type_id) {
851                Some((_guard, cap_ref)) => M::check(cap_ref),
852                None => crate::core::health::HealthStatus::Unhealthy {
853                    detail: "capability not found".to_string(),
854                },
855            }
856        });
857        self.health_checkers
858            .borrow_mut()
859            .insert(TypeId::of::<M>(), (M::NAME, checker));
860    }
861
862    // ─── Conditional Registration ───────────────────────────────────────
863
864    /// Conditionally register a module based on a runtime predicate.
865    ///
866    /// The predicate receives the current `Kit` (for inspecting configs
867    /// or other state). Returns `true` if the module was actually registered.
868    ///
869    /// # Errors
870    ///
871    /// Returns `TraitKitError::AlreadyRegistered` if the predicate returns
872    /// `true` but the module was already registered.
873    pub fn register_if<M: AutoBuilder>(
874        &mut self,
875        predicate: impl FnOnce(&Kit) -> bool,
876    ) -> Result<bool, TraitKitError> {
877        if predicate(self) {
878            self.register::<M>()?;
879            Ok(true)
880        } else {
881            Ok(false)
882        }
883    }
884
885    // ─── Feature Toggle ────────────────────────────────────────────────
886
887    /// Enable or disable a feature toggle.
888    ///
889    /// Requires the `toggle` feature. The toggle state is stored as a
890    /// `HashMap<String, bool>` and can be queried via `is_toggle_enabled`.
891    #[cfg(feature = "toggle")]
892    pub fn enable_toggle(&self, key: impl Into<String>, enabled: bool) {
893        self.toggles.borrow_mut().insert(key.into(), enabled);
894    }
895
896    /// Check if a feature toggle is enabled.
897    ///
898    /// Returns `false` for unknown keys.
899    #[cfg(feature = "toggle")]
900    pub fn is_toggle_enabled(&self, key: &str) -> bool {
901        self.toggles.borrow().get(key).copied().unwrap_or(false)
902    }
903
904    /// Conditionally register a module based on a feature toggle.
905    ///
906    /// Requires the `toggle` feature. Delegates to `register_if` with a
907    /// predicate that checks `is_toggle_enabled(key)`.
908    ///
909    /// # Errors
910    ///
911    /// Returns `TraitKitError::AlreadyRegistered` if the toggle is enabled
912    /// but the module was already registered.
913    #[cfg(feature = "toggle")]
914    pub fn register_if_toggle<M: AutoBuilder>(&mut self, key: &str) -> Result<bool, TraitKitError> {
915        let enabled = self.is_toggle_enabled(key);
916        if enabled {
917            self.register::<M>()?;
918        }
919        Ok(enabled)
920    }
921
922    // ─── Observability ─────────────────────────────────────────────────
923
924    /// Register a build observer that receives callbacks during `build()`.
925    ///
926    /// Requires the `observer` feature.
927    #[cfg(feature = "observer")]
928    pub fn with_observer(
929        &mut self,
930        observer: std::sync::Arc<dyn crate::core::observer::BuildObserver>,
931    ) {
932        self.observers.borrow_mut().push(observer);
933    }
934
935    // ─── Decorator ─────────────────────────────────────────────────────
936
937    /// Register a decorator for a module's capability.
938    ///
939    /// The decorator is applied after the module's capability is built,
940    /// wrapping or enhancing the original value. Multiple decorators can
941    /// be registered for the same module; they are applied in registration
942    /// order.
943    ///
944    /// Requires the `decorator` feature.
945    ///
946    /// # Panics
947    ///
948    /// Panics at runtime if the internal `downcast` fails due to a type
949    /// mismatch (should never happen when used correctly).
950    #[cfg(feature = "decorator")]
951    pub fn decorate<M: AutoBuilder>(
952        &self,
953        decorator: impl Fn(M::Capability) -> M::Capability + 'static,
954    ) where
955        M::Capability: 'static,
956    {
957        let wrapper: DecoratorFn = Box::new(move |boxed_cap| {
958            let cap = boxed_cap
959                .downcast::<M::Capability>()
960                .expect("decorator type mismatch");
961            let decorated = decorator(*cap);
962            Box::new(decorated) as Box<dyn Any>
963        });
964        self.decorators
965            .borrow_mut()
966            .entry(TypeId::of::<M::Capability>())
967            .or_default()
968            .push(wrapper);
969        // Record module TypeId → capability TypeId mapping so
970        // `build_eager_modules()` can look up decorators by module TypeId.
971        self.decorator_module_to_cap
972            .borrow_mut()
973            .insert(TypeId::of::<M>(), TypeId::of::<M::Capability>());
974    }
975}
976
977impl<S> Kit<S> {
978    /// Apply registered decorators for a capability (keyed by capability `TypeId`).
979    #[cfg(feature = "decorator")]
980    fn apply_decorators(&self, cap_type_id: TypeId, boxed: Box<dyn Any>) -> Box<dyn Any> {
981        let decorators = self.decorators.borrow();
982        let Some(dec_list) = decorators.get(&cap_type_id) else {
983            return boxed;
984        };
985        let mut current = boxed;
986        for dec in dec_list {
987            current = dec(current);
988        }
989        current
990    }
991
992    /// Retrieve a capability by its module type.
993    ///
994    /// Available on both `Kit<Unbuilt>` (inside `AutoBuilder::build` callbacks)
995    /// and `Kit<Ready>` (after `build()` completes).
996    ///
997    /// On `Kit<Ready>`, if the module was registered via `register_lazy`,
998    /// the first `require()` call triggers lazy construction: the stored
999    /// `build_fn` is invoked, the result is cached in a `OnceLock` cell,
1000    /// and subsequent calls return a clone from the cache without re-running
1001    /// the builder.
1002    ///
1003    /// # Errors
1004    ///
1005    /// Returns `TraitKitError::MissingCapability` if the module has not been built.
1006    /// Returns `TraitKitError::BuildFailed` if a lazy module's `build_fn` fails.
1007    pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, TraitKitError> {
1008        let type_id = TypeId::of::<M>();
1009
1010        // 1. Eager capabilities (already-built modules + overrides)
1011        if let Some(cap) = self
1012            .capabilities
1013            .get_cloned_by_type_id::<M::Capability>(type_id)
1014        {
1015            return Ok(cap);
1016        }
1017
1018        // 2. Lazy slots — check OnceLock cache (previously-built lazy modules)
1019        if let Some(cached) = Self::get_lazy_cached::<M>(self, type_id) {
1020            return Ok(cached);
1021        }
1022
1023        // 3. Lazy slots — first-access construction (cell empty, builder exists)
1024        // Take the builder out to release the RefCell borrow before calling it,
1025        // allowing the builder to re-enter require() for its own dependencies.
1026        let builder = self
1027            .lazy_slots
1028            .borrow_mut()
1029            .get_mut(&type_id)
1030            .and_then(|slot| slot.builder.take());
1031
1032        if let Some(builder) = builder {
1033            // SAFETY: `Kit<S>` has the same memory layout as `Kit<Unbuilt>`
1034            // because `S` only appears in `PhantomData<S>` (zero-sized, same
1035            // representation as `()`). `BuildFn` expects `&Kit<Unbuilt>`; we
1036            // hold `&Kit<S>`. The cast is sound for any `S` since the field
1037            // layout is identical. In practice, this code path is only reached
1038            // on `Kit<Ready>` (lazy_slots is only populated after `build()`),
1039            // but the cast is valid regardless.
1040            //
1041            // Compile-time layout assertion: if any field depending on `S` is
1042            // added to `Kit`, this will fail at compile time, catching the
1043            // unsoundness before runtime.
1044            const _: () = assert!(
1045                std::mem::size_of::<Kit<Ready>>() == std::mem::size_of::<Kit>(),
1046                "Kit layout changed; unsafe cast is no longer sound"
1047            );
1048            #[allow(unsafe_code)]
1049            let kit_ref: &Kit = unsafe { &*std::ptr::from_ref(self).cast::<Kit>() };
1050            let boxed = (builder)(kit_ref).map_err(|e| TraitKitError::BuildFailed {
1051                context: M::NAME.to_string(),
1052                source: e,
1053            })?;
1054            // Apply decorators (keyed by capability TypeId)
1055            #[cfg(feature = "decorator")]
1056            let boxed = self.apply_decorators(TypeId::of::<M::Capability>(), boxed);
1057            // Cache in OnceLock for future require() / require_ref() calls
1058            if let Some(slot) = self.lazy_slots.borrow().get(&type_id) {
1059                let _ = slot.cell.set(boxed);
1060            }
1061            return Self::get_lazy_cached::<M>(self, type_id).ok_or(
1062                TraitKitError::MissingCapability {
1063                    key: M::NAME.to_string(),
1064                },
1065            );
1066        }
1067
1068        // 4. Not found
1069        Err(TraitKitError::MissingCapability {
1070            key: M::NAME.to_string(),
1071        })
1072    }
1073
1074    /// Extracted helper: retrieve a cached lazy-slot value without rebuilding.
1075    /// Consolidates the duplicate lazy-cache lookup pattern in `require()`.
1076    fn get_lazy_cached<M: AutoBuilder>(&self, type_id: TypeId) -> Option<M::Capability> {
1077        self.lazy_slots
1078            .borrow()
1079            .get(&type_id)
1080            .and_then(|slot| slot.cell.get())
1081            .and_then(|b| b.downcast_ref::<M::Capability>().cloned())
1082    }
1083
1084    /// Retrieve all capabilities registered via `register_multi` for the
1085    /// given module type, in registration order.
1086    ///
1087    /// Available on both `Kit<Unbuilt>` and `Kit<Ready>`, but
1088    /// `multi_capabilities` is only populated after `build()`. Calling
1089    /// `require_all` before `build()` returns `MissingCapability`.
1090    ///
1091    /// # Errors
1092    ///
1093    /// Returns `TraitKitError::MissingCapability` if no multi-binding
1094    /// capabilities were registered for `M::Capability`.
1095    pub fn require_all<M: AutoBuilder>(&self) -> Result<Vec<M::Capability>, TraitKitError>
1096    where
1097        M::Capability: Clone + 'static,
1098    {
1099        let cap_id = TypeId::of::<M::Capability>();
1100        let multi = self.multi_capabilities.borrow();
1101        let vec = multi.get(&cap_id).ok_or(TraitKitError::MissingCapability {
1102            key: M::NAME.to_string(),
1103        })?;
1104
1105        let mut result = Vec::with_capacity(vec.len());
1106        for boxed in vec {
1107            let cap = boxed.downcast_ref::<M::Capability>().cloned().ok_or(
1108                TraitKitError::MissingCapability {
1109                    key: M::NAME.to_string(),
1110                },
1111            )?;
1112            result.push(cap);
1113        }
1114        Ok(result)
1115    }
1116
1117    /// Get a configuration value.
1118    ///
1119    /// # Errors
1120    ///
1121    /// Returns `TraitKitError::MissingConfig` if no value of type `C` was set.
1122    pub fn config<C: Clone + 'static>(&self) -> Result<C, TraitKitError> {
1123        self.configs
1124            .get_cloned::<C>()
1125            .ok_or(TraitKitError::MissingConfig {
1126                key: std::any::type_name::<C>().to_string(),
1127            })
1128    }
1129
1130    /// Subscribe a callback to be invoked when config of type `C` is reloaded.
1131    ///
1132    /// Requires the `reload` feature. The callback receives no
1133    /// arguments; use `Kit::config::<C>()` inside it to read the new value.
1134    /// Callbacks are stored in a `RefCell` (single-threaded, `!Sync`).
1135    ///
1136    /// Layer 2 of the inheritance system: cargo feature chain
1137    /// `reload` → `confers`.
1138    #[cfg(feature = "reload")]
1139    pub fn subscribe<C: 'static>(&self, callback: impl Fn() + 'static) {
1140        let callback: Rc<dyn Fn()> = Rc::new(callback);
1141        self.subscribers
1142            .borrow_mut()
1143            .entry(TypeId::of::<C>())
1144            .or_default()
1145            .push(callback);
1146    }
1147
1148    /// Reload a configuration via its `Configurable` implementation and
1149    /// notify all subscribers of type `C`.
1150    ///
1151    /// Requires the `reload` feature. Calls `C::load()`, stores
1152    /// the result via `set_config`, then invokes every `subscribe::<C>`
1153    /// callback. Errors from `load()` are mapped to `TraitKitError::BuildFailed`.
1154    ///
1155    /// # Panics
1156    ///
1157    /// The new config is stored *before* invoking callbacks. If a callback
1158    /// panics, the config has already been updated but remaining subscribers
1159    /// in the chain are skipped (panic unwinds through `reload_config`).
1160    /// Use `std::panic::catch_unwind` inside callbacks if you need to
1161    /// guarantee notification of all subscribers.
1162    ///
1163    /// # Errors
1164    ///
1165    /// Returns `TraitKitError::BuildFailed` if `Configurable::load` fails.
1166    #[cfg(feature = "reload")]
1167    pub fn reload_config<C: super::Configurable>(&self) -> Result<(), TraitKitError> {
1168        let config = C::load().map_err(|e| TraitKitError::BuildFailed {
1169            context: "reload_config".into(),
1170            source: e,
1171        })?;
1172        self.configs.insert(config);
1173        // Clone individual Rc pointers (ref-count increment only) with
1174        // pre-allocated Vec to avoid a full `.cloned()` pass.
1175        let callbacks: Vec<Rc<dyn Fn()>> = match self.subscribers.borrow().get(&TypeId::of::<C>()) {
1176            Some(subs) => subs.iter().map(Rc::clone).collect(),
1177            None => Vec::new(),
1178        };
1179        for cb in &callbacks {
1180            cb();
1181        }
1182        Ok(())
1183    }
1184
1185    /// Resolve a capability by its interface type.
1186    ///
1187    /// Retrieves an `Arc<I>` previously stored via `register_as<M>()`.
1188    /// The interface type `I` must be `?Sized + 'static` (e.g.,
1189    /// `dyn Logger`).
1190    ///
1191    /// Available on both `Kit<Unbuilt>` (inside `InterfaceBuilder::build`
1192    /// callbacks) and `Kit<Ready>` (after `build()` completes).
1193    ///
1194    /// # Errors
1195    ///
1196    /// Returns `TraitKitError::MissingCapability` if the interface has not
1197    /// been registered or built.
1198    #[cfg(feature = "interface")]
1199    pub fn resolve<I>(&self) -> Result<std::sync::Arc<I>, TraitKitError>
1200    where
1201        I: ?Sized + 'static,
1202    {
1203        let interface_id = TypeId::of::<I>();
1204        self.capabilities
1205            .get_cloned_by_type_id::<std::sync::Arc<I>>(interface_id)
1206            .ok_or(TraitKitError::MissingCapability {
1207                key: "interface".into(),
1208            })
1209    }
1210}
1211
1212impl Kit {
1213    /// Encrypt and store a configuration value.
1214    ///
1215    /// Requires the `encryption` feature. Serializes `value` to JSON,
1216    /// derives a per-field key from `master_key` and `C::PATH` via HKDF, then
1217    /// encrypts with XChaCha20-Poly1305. The resulting nonce + ciphertext is
1218    /// stored in `encrypted_configs`, separate from the plaintext `TypeMap`.
1219    ///
1220    /// Layer 3 of the inheritance system: the encryption key is bound to
1221    /// `ModuleConfig::PATH`, so the same master key produces different field
1222    /// keys for different modules.
1223    ///
1224    /// # Errors
1225    ///
1226    /// Returns `TraitKitError::BuildFailed` if serialization, key derivation, or
1227    /// encryption fails.
1228    #[cfg(feature = "encryption")]
1229    pub fn set_encrypted<C>(&self, value: &C, master_key: &[u8]) -> Result<(), TraitKitError>
1230    where
1231        C: super::ModuleConfig + serde::Serialize,
1232    {
1233        use super::XChaCha20Crypto;
1234
1235        // XChaCha20-Poly1305 requires a 256-bit (32-byte) key; HKDF needs
1236        // a reasonably sized input key material. Reject short keys early.
1237        if master_key.len() < 16 {
1238            return Err(TraitKitError::BuildFailed {
1239                context: "set_encrypted".into(),
1240                source: Box::new(std::io::Error::new(
1241                    std::io::ErrorKind::InvalidInput,
1242                    format!(
1243                        "master_key must be at least 16 bytes, got {}",
1244                        master_key.len()
1245                    ),
1246                )),
1247            });
1248        }
1249
1250        let plaintext = serde_json::to_vec(value).map_err(|e| TraitKitError::BuildFailed {
1251            context: "set_encrypted".into(),
1252            source: Box::new(e),
1253        })?;
1254
1255        let field_key = derive_kit_field_key(master_key, C::PATH, "set_encrypted")?;
1256
1257        let (nonce, ciphertext) = XChaCha20Crypto::new()
1258            .encrypt(&plaintext, &field_key)
1259            .map_err(|e| TraitKitError::BuildFailed {
1260                context: "set_encrypted".into(),
1261                source: Box::new(e),
1262            })?;
1263
1264        self.encrypted_configs
1265            .borrow_mut()
1266            .insert(TypeId::of::<C>(), EncryptedBlob::new(nonce, ciphertext));
1267        Ok(())
1268    }
1269
1270    /// Check if an encrypted config of type `C` is registered.
1271    #[cfg(feature = "encryption")]
1272    pub fn contains_encrypted<C: super::ModuleConfig>(&self) -> bool {
1273        self.encrypted_configs
1274            .borrow()
1275            .contains_key(&TypeId::of::<C>())
1276    }
1277
1278    /// Load a configuration via `Configurable::load`, falling back to
1279    /// `ModuleConfig::default_value` if loading fails.
1280    ///
1281    /// Requires the `confers` feature. Stores the resulting value
1282    /// via `set_config`, overriding any prior value of the same type.
1283    ///
1284    /// # Returns
1285    ///
1286    /// `true` if `C::load()` succeeded, `false` if the default was used.
1287    /// The return value lets callers detect fallback without inspecting the
1288    /// stored value.
1289    ///
1290    /// # Errors
1291    ///
1292    /// Currently never returns an error, but the `Result` is reserved for
1293    /// future use (e.g. validation of the default value).
1294    #[cfg(feature = "confers")]
1295    pub fn load_config_or_default<C>(&self) -> Result<bool, TraitKitError>
1296    where
1297        C: super::Configurable + super::ModuleConfig,
1298    {
1299        match C::load() {
1300            Ok(value) => {
1301                self.set_config(value);
1302                Ok(true)
1303            }
1304            Err(_e) => {
1305                self.set_config(C::default_value());
1306                Ok(false)
1307            }
1308        }
1309    }
1310}
1311
1312impl Kit<Ready> {
1313    /// Retrieve an optional capability. Returns `None` if not built.
1314    pub fn optional<M: AutoBuilder>(&self) -> Option<M::Capability> {
1315        let type_id = TypeId::of::<M>();
1316        self.capabilities
1317            .get_cloned_by_type_id::<M::Capability>(type_id)
1318    }
1319
1320    /// Retrieve a capability by reference, avoiding `Clone`.
1321    ///
1322    /// Unlike `require()`, this returns a `Ref` borrowing the stored value
1323    /// directly, with no clone overhead. The `Ref` holds a read lock on the
1324    /// interior `RefCell` — while it is alive, calling `reload_config` or
1325    /// any mutating method will panic (`borrow_mut` conflict). Keep the
1326    /// `Ref` lifetime short.
1327    ///
1328    /// # Errors
1329    ///
1330    /// Returns `TraitKitError::MissingCapability` if the module has not been built.
1331    pub fn require_ref<M: AutoBuilder>(
1332        &self,
1333    ) -> Result<std::cell::Ref<'_, M::Capability>, TraitKitError>
1334    where
1335        M::Capability: 'static,
1336    {
1337        use std::cell::Ref;
1338
1339        let type_id = TypeId::of::<M>();
1340        if !self.capabilities.contains_by_type_id(type_id) {
1341            return Err(TraitKitError::MissingCapability {
1342                key: M::NAME.to_string(),
1343            });
1344        }
1345        Ref::filter_map(self.capabilities.inner_ref(), |map| {
1346            map.get(&type_id)
1347                .and_then(|b| b.downcast_ref::<M::Capability>())
1348        })
1349        .map_err(|_| TraitKitError::MissingCapability {
1350            key: M::NAME.to_string(),
1351        })
1352    }
1353
1354    /// Check if a capability has been built.
1355    pub fn contains<M: AutoBuilder>(&self) -> bool {
1356        self.capabilities.contains_by_type_id(TypeId::of::<M>())
1357    }
1358
1359    /// Check if a config is registered.
1360    pub fn contains_config<C: Clone + 'static>(&self) -> bool {
1361        self.configs.contains::<C>()
1362    }
1363
1364    // ─── Feature Toggle (Ready state) ──────────────────────────────────
1365
1366    /// Check if a feature toggle is enabled (available after build).
1367    #[cfg(feature = "toggle")]
1368    pub fn is_toggle_enabled(&self, key: &str) -> bool {
1369        self.toggles.borrow().get(key).copied().unwrap_or(false)
1370    }
1371
1372    /// Enable or disable a feature toggle (available after build).
1373    #[cfg(feature = "toggle")]
1374    pub fn enable_toggle(&self, key: impl Into<String>, enabled: bool) {
1375        self.toggles.borrow_mut().insert(key.into(), enabled);
1376    }
1377
1378    // ─── Lifecycle: shutdown ───────────────────────────────────────────
1379
1380    /// Shut down all lifecycle modules in reverse topological order.
1381    ///
1382    /// Calls `on_shutdown` for each module registered via `register_lifecycle`.
1383    /// A failed shutdown does not prevent other modules from shutting down.
1384    ///
1385    /// Requires the `lifecycle` feature.
1386    #[cfg(feature = "lifecycle")]
1387    pub fn shutdown(&self) {
1388        let callbacks: Vec<(TypeId, ShutdownCallback)> =
1389            self.shutdown_callbacks.borrow_mut().drain(..).collect();
1390        // Reverse order: last built → first shut down
1391        for (_type_id, callback) in callbacks.iter().rev() {
1392            callback(&self.capabilities);
1393        }
1394    }
1395
1396    // ─── Health Check ──────────────────────────────────────────────────
1397
1398    /// Check the health of a specific module.
1399    ///
1400    /// Requires the `health` feature and the module to have been registered
1401    /// via `register_health_check::<M>()`.
1402    ///
1403    /// # Errors
1404    ///
1405    /// Returns `TraitKitError::MissingConfig` if no health checker is
1406    /// registered for `M`.
1407    #[cfg(feature = "health")]
1408    pub fn health_check<M: crate::core::health::HealthCheck>(
1409        &self,
1410    ) -> Result<crate::core::health::HealthStatus, TraitKitError> {
1411        let type_id = TypeId::of::<M>();
1412        let checkers = self.health_checkers.borrow();
1413        let (_name, checker) = checkers.get(&type_id).ok_or(TraitKitError::MissingConfig {
1414            key: M::NAME.to_string(),
1415        })?;
1416        Ok(checker(&self.capabilities))
1417    }
1418
1419    /// Generate a health report for all registered health checkers.
1420    ///
1421    /// Returns a list of `(module_name, HealthStatus)` pairs.
1422    ///
1423    /// Requires the `health` feature.
1424    #[cfg(feature = "health")]
1425    pub fn health_report(&self) -> Vec<(&'static str, crate::core::health::HealthStatus)> {
1426        let checkers = self.health_checkers.borrow();
1427        checkers
1428            .values()
1429            .map(|(name, checker)| (*name, checker(&self.capabilities)))
1430            .collect()
1431    }
1432
1433    // ─── Factory Pattern ───────────────────────────────────────────────
1434
1435    /// Create a factory closure that produces new instances on each call.
1436    ///
1437    /// Unlike `require()` which returns the singleton built during `build()`,
1438    /// the factory invokes `M::build()` on every call, producing a fresh
1439    /// instance each time.
1440    ///
1441    pub fn factory<M: AutoBuilder>(
1442        &self,
1443    ) -> impl Fn() -> Result<M::Capability, TraitKitError> + '_ {
1444        move || {
1445            // SAFETY: Kit<Ready> and Kit<Unbuilt> have identical memory layout
1446            // (S only appears in PhantomData<S>). BuildFn expects &Kit<Unbuilt>.
1447            #[allow(unsafe_code)]
1448            let kit_ref: &Kit = unsafe { &*std::ptr::from_ref::<Kit<Ready>>(self).cast::<Kit>() };
1449            M::build(kit_ref).map_err(|e| TraitKitError::BuildFailed {
1450                context: M::NAME.to_string(),
1451                source: Box::new(e),
1452            })
1453        }
1454    }
1455
1456    // ─── Scope ─────────────────────────────────────────────────────────
1457
1458    /// Create a new empty scope for per-request instance isolation.
1459    ///
1460    /// Requires the `scope` feature.
1461    #[cfg(feature = "scope")]
1462    #[must_use]
1463    pub fn create_scope(&self) -> super::scope::Scope {
1464        super::scope::Scope::new()
1465    }
1466
1467    // ─── Graph Visualization ───────────────────────────────────────────
1468
1469    /// Export the dependency graph as a Graphviz DOT string.
1470    #[must_use]
1471    pub fn graph_dot(&self) -> String {
1472        self.graph.to_dot()
1473    }
1474
1475    /// Export the dependency graph as a Mermaid flowchart string.
1476    #[must_use]
1477    pub fn graph_mermaid(&self) -> String {
1478        self.graph.to_mermaid()
1479    }
1480
1481    /// Retrieve and decrypt a configuration value.
1482    ///
1483    /// Requires the `encryption` feature. Looks up the encrypted
1484    /// blob for type `C`, derives the per-field key from `master_key` and
1485    /// `C::PATH`, decrypts with XChaCha20-Poly1305, then deserializes from
1486    /// JSON. The `master_key` must match the one passed to `set_encrypted`.
1487    ///
1488    /// # Errors
1489    ///
1490    /// Returns `TraitKitError::MissingConfig` if no encrypted blob for `C` exists.
1491    /// Returns `TraitKitError::BuildFailed` if key derivation, decryption, or
1492    /// deserialization fails (e.g. wrong master key, tampered ciphertext).
1493    #[cfg(feature = "encryption")]
1494    pub fn get_encrypted<C>(&self, master_key: &[u8]) -> Result<C, TraitKitError>
1495    where
1496        C: super::ModuleConfig + serde::de::DeserializeOwned,
1497    {
1498        use super::XChaCha20Crypto;
1499
1500        if master_key.len() < 16 {
1501            return Err(TraitKitError::BuildFailed {
1502                context: "get_encrypted".into(),
1503                source: Box::new(std::io::Error::new(
1504                    std::io::ErrorKind::InvalidInput,
1505                    format!(
1506                        "master_key must be at least 16 bytes, got {}",
1507                        master_key.len()
1508                    ),
1509                )),
1510            });
1511        }
1512
1513        let blob = self
1514            .encrypted_configs
1515            .borrow()
1516            .get(&TypeId::of::<C>())
1517            .cloned()
1518            .ok_or(TraitKitError::MissingConfig {
1519                key: std::any::type_name::<C>().to_string(),
1520            })?;
1521
1522        let field_key = derive_kit_field_key(master_key, C::PATH, "get_encrypted")?;
1523
1524        let plaintext = XChaCha20Crypto::new()
1525            .decrypt(blob.nonce(), blob.ciphertext(), &field_key)
1526            .map_err(|e| TraitKitError::BuildFailed {
1527                context: "get_encrypted".into(),
1528                source: Box::new(e),
1529            })?;
1530
1531        serde_json::from_slice(&plaintext).map_err(|e| TraitKitError::BuildFailed {
1532            context: "get_encrypted".into(),
1533            source: Box::new(e),
1534        })
1535    }
1536}
1537
1538impl Default for Kit {
1539    fn default() -> Self {
1540        Self::new()
1541    }
1542}
1543
1544impl std::fmt::Debug for Kit {
1545    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1546        f.debug_struct("Kit<Unbuilt>")
1547            .field("modules", &self.graph.entries().len())
1548            .field("configs", &self.configs.len())
1549            .finish()
1550    }
1551}
1552
1553impl std::fmt::Debug for Kit<Ready> {
1554    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1555        f.debug_struct("Kit<Ready>")
1556            .field("modules", &self.graph.entries().len())
1557            .field("configs", &self.configs.len())
1558            .finish()
1559    }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564    use super::*;
1565    use crate::core::{AutoBuilder, ModuleMeta};
1566    use std::sync::Arc;
1567    use std::sync::atomic::{AtomicUsize, Ordering};
1568
1569    // === Test fixtures ===
1570
1571    struct MockCapability;
1572    impl ModuleMeta for MockCapability {
1573        const NAME: &'static str = "mock";
1574        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1575            &[]
1576        }
1577    }
1578    impl AutoBuilder for MockCapability {
1579        type Capability = Arc<AtomicUsize>;
1580        type Error = TraitKitError;
1581        fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1582            Ok(Arc::new(AtomicUsize::new(0)))
1583        }
1584    }
1585
1586    struct DependentModule;
1587    impl ModuleMeta for DependentModule {
1588        const NAME: &'static str = "dependent";
1589        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1590            static DEPS: &[(&str, std::any::TypeId)] =
1591                &[("mock", std::any::TypeId::of::<MockCapability>())];
1592            DEPS
1593        }
1594    }
1595    impl AutoBuilder for DependentModule {
1596        type Capability = Arc<AtomicUsize>;
1597        type Error = TraitKitError;
1598        fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1599            Ok(Arc::new(AtomicUsize::new(0)))
1600        }
1601    }
1602
1603    // === T002 tests ===
1604
1605    #[test]
1606    fn overrides_field_is_empty_on_new() {
1607        let kit = Kit::new();
1608        assert_eq!(kit.overrides.borrow().len(), 0);
1609    }
1610
1611    #[test]
1612    fn overrides_field_is_empty_after_build() {
1613        let kit = Kit::new();
1614        assert_eq!(kit.overrides.borrow().len(), 0);
1615    }
1616
1617    // === T003 tests ===
1618
1619    #[test]
1620    fn override_module_inserts_into_overrides_map() {
1621        let kit = Kit::new();
1622        assert_eq!(kit.overrides.borrow().len(), 0);
1623        kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(42)));
1624        assert_eq!(kit.overrides.borrow().len(), 1);
1625    }
1626
1627    #[test]
1628    fn override_module_strict_succeeds_when_deps_registered() {
1629        let mut kit = Kit::new();
1630        // Register the dependency first
1631        kit.register::<MockCapability>().unwrap();
1632        // Now strict override of the dependent module should succeed
1633        let result = kit.override_module_strict::<DependentModule>(Arc::new(AtomicUsize::new(99)));
1634        assert!(result.is_ok());
1635        assert_eq!(kit.overrides.borrow().len(), 1);
1636    }
1637
1638    #[test]
1639    fn override_module_strict_fails_when_deps_missing() {
1640        let mut kit = Kit::new();
1641        // Do NOT register MockCapability first
1642        let result = kit.override_module_strict::<DependentModule>(Arc::new(AtomicUsize::new(99)));
1643        assert!(matches!(
1644            result,
1645            Err(TraitKitError::DependencyMissing {
1646                module: "dependent",
1647                missing: "mock"
1648            })
1649        ));
1650        // Override should not have been inserted
1651        assert_eq!(kit.overrides.borrow().len(), 0);
1652    }
1653
1654    // === T004 tests ===
1655
1656    /// Module whose `build_fn` increments a counter, to verify override skips it.
1657    struct CountingModule;
1658    impl ModuleMeta for CountingModule {
1659        const NAME: &'static str = "counting";
1660        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1661            &[]
1662        }
1663    }
1664    impl AutoBuilder for CountingModule {
1665        type Capability = Arc<AtomicUsize>;
1666        type Error = TraitKitError;
1667        fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1668            // Return a counter that starts at 0; the test checks the counter
1669            // value to distinguish "build_fn ran" from "override used".
1670            Ok(Arc::new(AtomicUsize::new(0)))
1671        }
1672    }
1673
1674    #[test]
1675    fn build_uses_override_and_skips_build_fn() {
1676        let kit = Kit::new();
1677        // Register the module (so it's in the graph and gets sorted)
1678        let mut kit = kit;
1679        kit.register::<CountingModule>().unwrap();
1680        // Override with a capability value of 42
1681        kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(42)));
1682        // Build
1683        let built = kit.build().unwrap();
1684        // require() should return the override value (42), not the build_fn value (0)
1685        let cap = built.require::<CountingModule>().unwrap();
1686        assert_eq!(cap.load(Ordering::SeqCst), 42);
1687    }
1688
1689    #[test]
1690    fn build_uses_build_fn_when_no_override() {
1691        let mut kit = Kit::new();
1692        kit.register::<CountingModule>().unwrap();
1693        // No override — build_fn should run and produce value 0
1694        let built = kit.build().unwrap();
1695        let cap = built.require::<CountingModule>().unwrap();
1696        assert_eq!(cap.load(Ordering::SeqCst), 0);
1697    }
1698
1699    #[test]
1700    fn build_inserts_unregistered_override_after_topo_loop() {
1701        // override_module allows injecting a module that was NOT registered.
1702        // build() should still make it available via require().
1703        let kit = Kit::new();
1704        kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(77)));
1705        let built = kit.build().unwrap();
1706        let cap = built.require::<MockCapability>().unwrap();
1707        assert_eq!(cap.load(Ordering::SeqCst), 77);
1708    }
1709
1710    // === T005 tests ===
1711
1712    #[test]
1713    fn require_ref_returns_reference_to_built_capability() {
1714        let mut kit = Kit::new();
1715        kit.register::<CountingModule>().unwrap();
1716        let built = kit.build().unwrap();
1717        let r = built.require_ref::<CountingModule>().unwrap();
1718        // build_fn returns Arc<AtomicUsize::new(0)>
1719        assert_eq!((*r).load(Ordering::SeqCst), 0);
1720    }
1721
1722    #[test]
1723    fn require_ref_returns_override_value() {
1724        let mut kit = Kit::new();
1725        kit.register::<CountingModule>().unwrap();
1726        kit.override_module::<CountingModule>(Arc::new(AtomicUsize::new(55)));
1727        let built = kit.build().unwrap();
1728        let r = built.require_ref::<CountingModule>().unwrap();
1729        assert_eq!((*r).load(Ordering::SeqCst), 55);
1730    }
1731
1732    #[test]
1733    fn require_ref_returns_missing_capability_for_unbuilt() {
1734        let kit = Kit::new();
1735        let built = kit.build().unwrap();
1736        let result = built.require_ref::<CountingModule>();
1737        assert!(matches!(
1738            result,
1739            Err(TraitKitError::MissingCapability { ref key }) if key == "counting"
1740        ));
1741    }
1742
1743    // === T007 tests ===
1744
1745    #[test]
1746    fn register_lazy_does_not_build_during_build() {
1747        let mut kit = Kit::new();
1748        kit.register_lazy::<CountingModule>().unwrap();
1749        // build() should succeed without triggering CountingModule's build_fn
1750        let built = kit.build().unwrap();
1751        // The capability should NOT be available (lazy not yet triggered)
1752        assert!(!built.contains::<CountingModule>());
1753    }
1754
1755    #[test]
1756    fn register_lazy_adds_to_dependency_graph() {
1757        let mut kit = Kit::new();
1758        // Register dependency first
1759        kit.register::<MockCapability>().unwrap();
1760        // Register lazy module that depends on MockCapability
1761        kit.register_lazy::<DependentModule>().unwrap();
1762        // build() should succeed (graph validation passes)
1763        let built = kit.build().unwrap();
1764        // MockCapability should be built (eager), DependentModule should NOT (lazy)
1765        assert!(built.contains::<MockCapability>());
1766        assert!(!built.contains::<DependentModule>());
1767    }
1768
1769    #[test]
1770    fn register_lazy_returns_already_registered_for_duplicate() {
1771        let mut kit = Kit::new();
1772        kit.register_lazy::<CountingModule>().unwrap();
1773        let result = kit.register_lazy::<CountingModule>();
1774        assert!(matches!(
1775            result,
1776            Err(TraitKitError::AlreadyRegistered { module: "counting" })
1777        ));
1778    }
1779
1780    // === T008 tests ===
1781
1782    #[test]
1783    fn lazy_slots_empty_on_new_kit() {
1784        let kit = Kit::new();
1785        assert_eq!(kit.lazy_slots.borrow().len(), 0);
1786    }
1787
1788    #[test]
1789    fn build_transfers_lazy_builders_to_lazy_slots() {
1790        let mut kit = Kit::new();
1791        kit.register_lazy::<CountingModule>().unwrap();
1792        assert_eq!(kit.lazy_builders.borrow().len(), 1);
1793        assert_eq!(kit.lazy_slots.borrow().len(), 0);
1794
1795        let built = kit.build().unwrap();
1796
1797        // After build(): lazy_builders drained, lazy_slots populated
1798        assert_eq!(built.lazy_builders.borrow().len(), 0);
1799        assert_eq!(built.lazy_slots.borrow().len(), 1);
1800        assert!(
1801            built
1802                .lazy_slots
1803                .borrow()
1804                .contains_key(&TypeId::of::<CountingModule>())
1805        );
1806    }
1807
1808    #[test]
1809    fn lazy_slots_cells_empty_after_build() {
1810        let mut kit = Kit::new();
1811        kit.register_lazy::<CountingModule>().unwrap();
1812        let built = kit.build().unwrap();
1813
1814        // The OnceLock cell should be empty (not yet constructed) — first
1815        // access via require() (T009) will populate it.
1816        let slots = built.lazy_slots.borrow();
1817        let slot = slots
1818            .get(&TypeId::of::<CountingModule>())
1819            .expect("slot exists");
1820        assert!(slot.cell.get().is_none());
1821    }
1822
1823    #[test]
1824    fn build_transfers_multiple_lazy_builders_to_lazy_slots() {
1825        let mut kit = Kit::new();
1826        kit.register::<MockCapability>().unwrap();
1827        kit.register_lazy::<DependentModule>().unwrap();
1828        kit.register_lazy::<CountingModule>().unwrap();
1829        assert_eq!(kit.lazy_builders.borrow().len(), 2);
1830
1831        let built = kit.build().unwrap();
1832
1833        assert_eq!(built.lazy_builders.borrow().len(), 0);
1834        assert_eq!(built.lazy_slots.borrow().len(), 2);
1835        assert!(
1836            built
1837                .lazy_slots
1838                .borrow()
1839                .contains_key(&TypeId::of::<DependentModule>())
1840        );
1841        assert!(
1842            built
1843                .lazy_slots
1844                .borrow()
1845                .contains_key(&TypeId::of::<CountingModule>())
1846        );
1847    }
1848
1849    // === T009 tests ===
1850
1851    #[test]
1852    fn require_triggers_lazy_construction_on_first_access() {
1853        let mut kit = Kit::new();
1854        kit.register_lazy::<CountingModule>().unwrap();
1855        let built = kit.build().unwrap();
1856
1857        // Before require: capability not in capabilities map
1858        assert!(!built.contains::<CountingModule>());
1859
1860        // First require should trigger lazy construction
1861        let cap = built.require::<CountingModule>().unwrap();
1862        assert_eq!(cap.load(Ordering::SeqCst), 0);
1863    }
1864
1865    #[test]
1866    fn require_does_not_rebuild_lazy_on_second_call() {
1867        // Local static counter — each test function has its own COUNT
1868        static COUNT: AtomicUsize = AtomicUsize::new(0);
1869
1870        struct CountedModule;
1871        impl ModuleMeta for CountedModule {
1872            const NAME: &'static str = "test-counted";
1873            fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1874                &[]
1875            }
1876        }
1877        impl AutoBuilder for CountedModule {
1878            type Capability = Arc<AtomicUsize>;
1879            type Error = TraitKitError;
1880            fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1881                let n = COUNT.fetch_add(1, Ordering::SeqCst);
1882                Ok(Arc::new(AtomicUsize::new(n)))
1883            }
1884        }
1885
1886        COUNT.store(0, Ordering::SeqCst);
1887        let mut kit = Kit::new();
1888        kit.register_lazy::<CountedModule>().unwrap();
1889        let built = kit.build().unwrap();
1890
1891        let cap1 = built.require::<CountedModule>().unwrap();
1892        let cap2 = built.require::<CountedModule>().unwrap();
1893
1894        // Both calls should return the same value (builder called once)
1895        assert_eq!(
1896            cap1.load(Ordering::SeqCst),
1897            0,
1898            "first require returns count 0"
1899        );
1900        assert_eq!(
1901            cap2.load(Ordering::SeqCst),
1902            0,
1903            "second require returns same count"
1904        );
1905        assert_eq!(
1906            COUNT.load(Ordering::SeqCst),
1907            1,
1908            "builder invoked exactly once"
1909        );
1910    }
1911
1912    #[test]
1913    fn require_lazy_with_registered_dependency_succeeds() {
1914        // A lazy module that calls kit.require() for its dependency in build()
1915        struct LazyDependentModule;
1916        impl ModuleMeta for LazyDependentModule {
1917            const NAME: &'static str = "lazy-dependent";
1918            fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1919                static DEPS: &[(&str, std::any::TypeId)] =
1920                    &[("mock", std::any::TypeId::of::<MockCapability>())];
1921                DEPS
1922            }
1923        }
1924        impl AutoBuilder for LazyDependentModule {
1925            type Capability = Arc<AtomicUsize>;
1926            type Error = TraitKitError;
1927            fn build(kit: &Kit) -> Result<Self::Capability, Self::Error> {
1928                // Verify the eager dependency is accessible during lazy build
1929                let mock = kit.require::<MockCapability>()?;
1930                Ok(Arc::new(AtomicUsize::new(
1931                    mock.load(Ordering::SeqCst) + 100,
1932                )))
1933            }
1934        }
1935
1936        let mut kit = Kit::new();
1937        // Register MockCapability (adds to dependency graph) then override
1938        // with value 42 to verify it's accessible during lazy build
1939        kit.register::<MockCapability>().unwrap();
1940        kit.override_module::<MockCapability>(Arc::new(AtomicUsize::new(42)));
1941        kit.register_lazy::<LazyDependentModule>().unwrap();
1942        let built = kit.build().unwrap();
1943
1944        // First require triggers lazy build, which calls require::<MockCapability>()
1945        let cap = built.require::<LazyDependentModule>().unwrap();
1946        assert_eq!(
1947            cap.load(Ordering::SeqCst),
1948            142,
1949            "lazy build accessed eager dep (42 + 100)"
1950        );
1951    }
1952
1953    // === T010 tests ===
1954
1955    /// Multi-binding module A (capability = Arc<AtomicUsize>).
1956    struct MultiModuleA;
1957    impl ModuleMeta for MultiModuleA {
1958        const NAME: &'static str = "multi-a";
1959        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1960            &[]
1961        }
1962    }
1963    impl AutoBuilder for MultiModuleA {
1964        type Capability = Arc<AtomicUsize>;
1965        type Error = TraitKitError;
1966        fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1967            Ok(Arc::new(AtomicUsize::new(10)))
1968        }
1969    }
1970
1971    /// Multi-binding module B (same capability type as `MultiModuleA`).
1972    struct MultiModuleB;
1973    impl ModuleMeta for MultiModuleB {
1974        const NAME: &'static str = "multi-b";
1975        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1976            &[]
1977        }
1978    }
1979    impl AutoBuilder for MultiModuleB {
1980        type Capability = Arc<AtomicUsize>;
1981        type Error = TraitKitError;
1982        fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1983            Ok(Arc::new(AtomicUsize::new(20)))
1984        }
1985    }
1986
1987    /// Multi-binding module C (same capability type as `MultiModuleA`).
1988    struct MultiModuleC;
1989    impl ModuleMeta for MultiModuleC {
1990        const NAME: &'static str = "multi-c";
1991        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
1992            &[]
1993        }
1994    }
1995    impl AutoBuilder for MultiModuleC {
1996        type Capability = Arc<AtomicUsize>;
1997        type Error = TraitKitError;
1998        fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
1999            Ok(Arc::new(AtomicUsize::new(30)))
2000        }
2001    }
2002
2003    #[test]
2004    fn multi_builders_empty_on_new_kit() {
2005        let kit = Kit::new();
2006        assert_eq!(kit.multi_builders.borrow().len(), 0);
2007    }
2008
2009    #[test]
2010    fn register_multi_adds_to_multi_builders() {
2011        let mut kit = Kit::new();
2012        assert_eq!(kit.multi_builders.borrow().len(), 0);
2013
2014        kit.register_multi::<MultiModuleA>().unwrap();
2015
2016        // Keyed by TypeId::of::<M::Capability>() = TypeId::of::<Arc<AtomicUsize>>()
2017        let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2018        assert_eq!(kit.multi_builders.borrow().len(), 1);
2019        assert!(kit.multi_builders.borrow().contains_key(&cap_id));
2020        assert_eq!(
2021            kit.multi_builders.borrow().get(&cap_id).unwrap().len(),
2022            1,
2023            "first register_multi should produce Vec of length 1"
2024        );
2025    }
2026
2027    #[test]
2028    fn register_multi_three_times_appends_to_vec() {
2029        let mut kit = Kit::new();
2030        kit.register_multi::<MultiModuleA>().unwrap();
2031        kit.register_multi::<MultiModuleB>().unwrap();
2032        kit.register_multi::<MultiModuleC>().unwrap();
2033
2034        let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2035        let builders = kit.multi_builders.borrow();
2036        let vec = builders.get(&cap_id).expect("cap_id exists");
2037        assert_eq!(
2038            vec.len(),
2039            3,
2040            "three register_multi calls should produce Vec of length 3"
2041        );
2042    }
2043
2044    #[test]
2045    fn register_multi_adds_module_to_dependency_graph() {
2046        let mut kit = Kit::new();
2047        kit.register_multi::<MultiModuleA>().unwrap();
2048
2049        // The module type_id (not cap_id) should be in the graph
2050        assert!(kit.graph.name_of(TypeId::of::<MultiModuleA>()).is_some());
2051    }
2052
2053    #[test]
2054    fn register_multi_returns_already_registered_for_duplicate_module() {
2055        let mut kit = Kit::new();
2056        kit.register_multi::<MultiModuleA>().unwrap();
2057
2058        let result = kit.register_multi::<MultiModuleA>();
2059        assert!(matches!(
2060            result,
2061            Err(TraitKitError::AlreadyRegistered { module: "multi-a" })
2062        ));
2063    }
2064
2065    #[test]
2066    fn register_multi_returns_already_registered_if_already_registered_via_register() {
2067        let mut kit = Kit::new();
2068        kit.register::<MockCapability>().unwrap();
2069
2070        let result = kit.register_multi::<MockCapability>();
2071        assert!(matches!(
2072            result,
2073            Err(TraitKitError::AlreadyRegistered { module: "mock" })
2074        ));
2075    }
2076
2077    #[test]
2078    fn register_multi_coexists_with_register_for_different_modules() {
2079        let mut kit = Kit::new();
2080        kit.register::<MockCapability>().unwrap();
2081        kit.register_multi::<MultiModuleA>().unwrap();
2082        kit.register_multi::<MultiModuleB>().unwrap();
2083
2084        // MockCapability in builders, MultiModuleA/B in multi_builders
2085        assert!(
2086            kit.builders
2087                .borrow()
2088                .contains_key(&TypeId::of::<MockCapability>())
2089        );
2090        let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2091        assert_eq!(kit.multi_builders.borrow().get(&cap_id).unwrap().len(), 2);
2092    }
2093
2094    // === T011 tests ===
2095
2096    #[test]
2097    fn require_all_returns_empty_for_unregistered_capability() {
2098        let mut kit = Kit::new();
2099        // Register MockCapability (eager, not multi)
2100        kit.register::<MockCapability>().unwrap();
2101        let built = kit.build().unwrap();
2102
2103        // require_all for a capability with no multi-binding registrations
2104        let result = built.require_all::<MultiModuleA>();
2105        assert!(matches!(
2106            result,
2107            Err(TraitKitError::MissingCapability { ref key }) if key == "multi-a"
2108        ));
2109    }
2110
2111    #[test]
2112    fn require_all_returns_vec_of_three_after_three_register_multi() {
2113        let mut kit = Kit::new();
2114        kit.register_multi::<MultiModuleA>().unwrap();
2115        kit.register_multi::<MultiModuleB>().unwrap();
2116        kit.register_multi::<MultiModuleC>().unwrap();
2117        let built = kit.build().unwrap();
2118
2119        let caps = built.require_all::<MultiModuleA>().unwrap();
2120        assert_eq!(
2121            caps.len(),
2122            3,
2123            "three register_multi calls should return Vec of length 3"
2124        );
2125    }
2126
2127    #[test]
2128    fn require_all_preserves_registration_order() {
2129        let mut kit = Kit::new();
2130        kit.register_multi::<MultiModuleA>().unwrap(); // builds value 10
2131        kit.register_multi::<MultiModuleB>().unwrap(); // builds value 20
2132        kit.register_multi::<MultiModuleC>().unwrap(); // builds value 30
2133        let built = kit.build().unwrap();
2134
2135        let caps = built.require_all::<MultiModuleA>().unwrap();
2136        assert_eq!(caps.len(), 3);
2137        // Verify order matches registration: 10, 20, 30
2138        assert_eq!(
2139            caps[0].load(Ordering::SeqCst),
2140            10,
2141            "first cap should be 10 (MultiModuleA)"
2142        );
2143        assert_eq!(
2144            caps[1].load(Ordering::SeqCst),
2145            20,
2146            "second cap should be 20 (MultiModuleB)"
2147        );
2148        assert_eq!(
2149            caps[2].load(Ordering::SeqCst),
2150            30,
2151            "third cap should be 30 (MultiModuleC)"
2152        );
2153    }
2154
2155    #[test]
2156    fn require_all_returns_missing_capability_before_build() {
2157        let mut kit = Kit::new();
2158        kit.register_multi::<MultiModuleA>().unwrap();
2159        // Don't call build() — multi_capabilities is empty
2160
2161        let result = kit.require_all::<MultiModuleA>();
2162        assert!(matches!(
2163            result,
2164            Err(TraitKitError::MissingCapability { ref key }) if key == "multi-a"
2165        ));
2166    }
2167
2168    #[test]
2169    fn build_drains_multi_builders_into_multi_capabilities() {
2170        let mut kit = Kit::new();
2171        kit.register_multi::<MultiModuleA>().unwrap();
2172        kit.register_multi::<MultiModuleB>().unwrap();
2173
2174        // Before build: multi_builders has entries, multi_capabilities is empty
2175        assert_eq!(kit.multi_builders.borrow().len(), 1); // one cap_id key
2176        assert_eq!(kit.multi_capabilities.borrow().len(), 0);
2177
2178        let built = kit.build().unwrap();
2179
2180        // After build: multi_builders is drained, multi_capabilities is populated
2181        assert_eq!(built.multi_builders.borrow().len(), 0);
2182        assert_eq!(built.multi_capabilities.borrow().len(), 1);
2183        let cap_id = TypeId::of::<Arc<AtomicUsize>>();
2184        assert_eq!(
2185            built
2186                .multi_capabilities
2187                .borrow()
2188                .get(&cap_id)
2189                .unwrap()
2190                .len(),
2191            2
2192        );
2193    }
2194
2195    #[test]
2196    fn require_all_coexists_with_require_for_single_binding() {
2197        let mut kit = Kit::new();
2198        // Single binding: MockCapability (eager)
2199        kit.register::<MockCapability>().unwrap();
2200        // Multi-binding: MultiModuleA, MultiModuleB
2201        kit.register_multi::<MultiModuleA>().unwrap();
2202        kit.register_multi::<MultiModuleB>().unwrap();
2203        let built = kit.build().unwrap();
2204
2205        // require gets the single binding
2206        let single = built.require::<MockCapability>().unwrap();
2207        assert_eq!(single.load(Ordering::SeqCst), 0);
2208
2209        // require_all gets the multi-binding (returns MultiModuleA's cap type)
2210        let multi = built.require_all::<MultiModuleA>().unwrap();
2211        assert_eq!(multi.len(), 2);
2212        assert_eq!(multi[0].load(Ordering::SeqCst), 10);
2213        assert_eq!(multi[1].load(Ordering::SeqCst), 20);
2214    }
2215
2216    #[test]
2217    fn multi_binding_build_error_returns_build_failed() {
2218        struct FailMultiModule;
2219        impl ModuleMeta for FailMultiModule {
2220            const NAME: &'static str = "fail-multi";
2221            fn dependencies() -> &'static [(&'static str, TypeId)] {
2222                &[]
2223            }
2224        }
2225        impl AutoBuilder for FailMultiModule {
2226            type Capability = Arc<AtomicUsize>;
2227            type Error = TraitKitError;
2228            fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2229                Err(TraitKitError::BuildFailed {
2230                    context: "fail-multi".into(),
2231                    source: Box::new(std::io::Error::other("multi fail")),
2232                })
2233            }
2234        }
2235
2236        let mut kit = Kit::new();
2237        kit.register_multi::<FailMultiModule>().unwrap();
2238        let result = kit.build();
2239        assert!(result.is_err());
2240        assert!(matches!(
2241            result.unwrap_err(),
2242            TraitKitError::BuildFailed { .. }
2243        ));
2244    }
2245}
2246
2247#[cfg(all(test, feature = "interface"))]
2248mod interface_tests {
2249    use super::*;
2250    use crate::core::{InterfaceBuilder, ModuleMeta};
2251    use std::sync::Arc;
2252    use std::sync::atomic::{AtomicUsize, Ordering};
2253
2254    // === Test fixtures ===
2255
2256    /// Test interface trait.
2257    trait Logger: 'static {
2258        fn log(&self, msg: &str) -> String;
2259    }
2260
2261    /// First Logger implementation.
2262    struct ConsoleLogger;
2263
2264    impl Logger for ConsoleLogger {
2265        fn log(&self, msg: &str) -> String {
2266            format!("[console] {msg}")
2267        }
2268    }
2269
2270    /// Second Logger implementation (for duplicate interface test).
2271    struct FileLogger;
2272
2273    impl Logger for FileLogger {
2274        fn log(&self, msg: &str) -> String {
2275            format!("[file] {msg}")
2276        }
2277    }
2278
2279    /// Test error type.
2280    #[derive(Debug)]
2281    struct InterfaceTestError;
2282
2283    impl std::fmt::Display for InterfaceTestError {
2284        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2285            write!(f, "interface test error")
2286        }
2287    }
2288
2289    impl std::error::Error for InterfaceTestError {}
2290
2291    /// Module providing `ConsoleLogger` behind dyn Logger.
2292    struct ConsoleLoggerModule;
2293
2294    impl ModuleMeta for ConsoleLoggerModule {
2295        const NAME: &'static str = "console-logger-iface";
2296        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
2297            &[]
2298        }
2299    }
2300
2301    impl InterfaceBuilder for ConsoleLoggerModule {
2302        type Interface = dyn Logger;
2303        type Capability = Arc<ConsoleLogger>;
2304        type Error = InterfaceTestError;
2305
2306        fn build(_kit: &Kit) -> Result<Arc<ConsoleLogger>, InterfaceTestError> {
2307            Ok(Arc::new(ConsoleLogger))
2308        }
2309
2310        fn into_interface(cap: Arc<ConsoleLogger>) -> Arc<dyn Logger> {
2311            cap
2312        }
2313    }
2314
2315    /// Module providing `FileLogger` behind dyn Logger (same interface).
2316    struct FileLoggerModule;
2317
2318    impl ModuleMeta for FileLoggerModule {
2319        const NAME: &'static str = "file-logger";
2320        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
2321            &[]
2322        }
2323    }
2324
2325    impl InterfaceBuilder for FileLoggerModule {
2326        type Interface = dyn Logger;
2327        type Capability = Arc<FileLogger>;
2328        type Error = InterfaceTestError;
2329
2330        fn build(_kit: &Kit) -> Result<Arc<FileLogger>, InterfaceTestError> {
2331            Ok(Arc::new(FileLogger))
2332        }
2333
2334        fn into_interface(cap: Arc<FileLogger>) -> Arc<dyn Logger> {
2335            cap
2336        }
2337    }
2338
2339    // === Tests ===
2340
2341    #[test]
2342    fn register_as_then_resolve_returns_arc_dyn_trait() {
2343        let mut kit = Kit::new();
2344        kit.register_as::<ConsoleLoggerModule>()
2345            .expect("register_as succeeds");
2346        let built = kit.build().expect("build succeeds");
2347
2348        let logger: Arc<dyn Logger> = built.resolve::<dyn Logger>().expect("resolve succeeds");
2349        assert_eq!(logger.log("hello"), "[console] hello");
2350    }
2351
2352    #[test]
2353    fn register_as_twice_same_interface_returns_already_registered() {
2354        let mut kit = Kit::new();
2355        kit.register_as::<ConsoleLoggerModule>()
2356            .expect("first register_as succeeds");
2357        let err = kit.register_as::<FileLoggerModule>().unwrap_err();
2358        assert!(
2359            matches!(err, TraitKitError::AlreadyRegistered { .. }),
2360            "expected AlreadyRegistered, got {err:?}"
2361        );
2362    }
2363
2364    #[test]
2365    fn resolve_before_build_returns_missing_capability() {
2366        let mut kit = Kit::new();
2367        kit.register_as::<ConsoleLoggerModule>()
2368            .expect("register_as succeeds");
2369        // resolve on unbuilt kit — capabilities is empty
2370        assert!(kit.resolve::<dyn Logger>().is_err());
2371    }
2372
2373    #[test]
2374    fn resolve_unregistered_interface_returns_missing_capability() {
2375        let kit = Kit::new();
2376        let built = kit.build().expect("build succeeds");
2377        assert!(built.resolve::<dyn Logger>().is_err());
2378    }
2379
2380    #[test]
2381    fn register_as_builds_during_build() {
2382        let mut kit = Kit::new();
2383        kit.register_as::<ConsoleLoggerModule>()
2384            .expect("register_as succeeds");
2385        let built = kit.build().expect("build succeeds");
2386        // After build, resolve should return the built capability
2387        let logger = built.resolve::<dyn Logger>().expect("resolve succeeds");
2388        assert_eq!(logger.log("test"), "[console] test");
2389    }
2390
2391    #[test]
2392    fn resolve_returns_callable_trait_object() {
2393        let mut kit = Kit::new();
2394        kit.register_as::<ConsoleLoggerModule>()
2395            .expect("register_as succeeds");
2396        let built = kit.build().expect("build succeeds");
2397
2398        let logger: Arc<dyn Logger> = built.resolve().expect("resolve succeeds");
2399        let result = logger.log("world");
2400        assert_eq!(result, "[console] world");
2401    }
2402
2403    #[test]
2404    fn register_as_coexists_with_register() {
2405        // register (AutoBuilder) + register_as (InterfaceBuilder) for
2406        // different modules should coexist.
2407        struct RegularModule;
2408        impl ModuleMeta for RegularModule {
2409            const NAME: &'static str = "regular";
2410            fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
2411                &[]
2412            }
2413        }
2414        impl AutoBuilder for RegularModule {
2415            type Capability = Arc<AtomicUsize>;
2416            type Error = TraitKitError;
2417            fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2418                Ok(Arc::new(AtomicUsize::new(42)))
2419            }
2420        }
2421
2422        let mut kit = Kit::new();
2423        kit.register::<RegularModule>().expect("register succeeds");
2424        kit.register_as::<ConsoleLoggerModule>()
2425            .expect("register_as succeeds");
2426        let built = kit.build().expect("build succeeds");
2427
2428        // Both retrieve correctly
2429        let cap = built.require::<RegularModule>().expect("require succeeds");
2430        assert_eq!(cap.load(Ordering::SeqCst), 42);
2431
2432        let logger = built.resolve::<dyn Logger>().expect("resolve succeeds");
2433        assert_eq!(logger.log("coexist"), "[console] coexist");
2434    }
2435
2436    #[test]
2437    fn register_as_same_module_twice_returns_already_registered() {
2438        let mut kit = Kit::new();
2439        kit.register_as::<ConsoleLoggerModule>()
2440            .expect("first register_as succeeds");
2441        // Same module type — graph.add() rejects duplicate
2442        let err = kit.register_as::<ConsoleLoggerModule>().unwrap_err();
2443        assert!(
2444            matches!(err, TraitKitError::AlreadyRegistered { .. }),
2445            "expected AlreadyRegistered, got {err:?}"
2446        );
2447    }
2448
2449    #[test]
2450    fn file_logger_interface_build_and_resolve() {
2451        let mut kit = Kit::new();
2452        kit.register_as::<FileLoggerModule>()
2453            .expect("register_as succeeds");
2454        let built = kit.build().expect("build succeeds");
2455        let logger: Arc<dyn Logger> = built.resolve::<dyn Logger>().expect("resolve succeeds");
2456        assert_eq!(logger.log("hello"), "[file] hello");
2457    }
2458
2459    #[test]
2460    fn interface_test_error_display() {
2461        let e = InterfaceTestError;
2462        assert_eq!(format!("{e}"), "interface test error");
2463    }
2464
2465    #[test]
2466    fn interface_build_error_returns_build_failed() {
2467        struct FailIfaceModule;
2468        impl ModuleMeta for FailIfaceModule {
2469            const NAME: &'static str = "fail-iface";
2470            fn dependencies() -> &'static [(&'static str, TypeId)] {
2471                &[]
2472            }
2473        }
2474        impl InterfaceBuilder for FailIfaceModule {
2475            type Interface = dyn Logger;
2476            type Capability = Arc<()>;
2477            type Error = InterfaceTestError;
2478            fn build(_kit: &Kit) -> Result<Arc<()>, InterfaceTestError> {
2479                Err(InterfaceTestError)
2480            }
2481            fn into_interface(_cap: Arc<()>) -> Arc<dyn Logger> {
2482                unreachable!()
2483            }
2484        }
2485
2486        let mut kit = Kit::new();
2487        kit.register_as::<FailIfaceModule>().unwrap();
2488        let result = kit.build();
2489        assert!(result.is_err());
2490        assert!(matches!(
2491            result.unwrap_err(),
2492            TraitKitError::BuildFailed { .. }
2493        ));
2494    }
2495}
2496
2497// ─── Feature-gated integration tests ─────────────────────────────────────
2498
2499#[cfg(all(test, feature = "lifecycle"))]
2500mod lifecycle_tests {
2501    use super::*;
2502    use crate::core::ModuleMeta;
2503    use crate::core::lifecycle::Lifecycle;
2504    use std::sync::Arc;
2505    use std::sync::atomic::{AtomicUsize, Ordering};
2506
2507    static LC_SHUTDOWN: AtomicUsize = AtomicUsize::new(0);
2508    static LC_READY: AtomicUsize = AtomicUsize::new(0);
2509
2510    struct LcModule;
2511    impl ModuleMeta for LcModule {
2512        const NAME: &'static str = "lc-module";
2513        fn dependencies() -> &'static [(&'static str, TypeId)] {
2514            &[]
2515        }
2516    }
2517    impl AutoBuilder for LcModule {
2518        type Capability = Arc<AtomicUsize>;
2519        type Error = TraitKitError;
2520        fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2521            Ok(Arc::new(AtomicUsize::new(0)))
2522        }
2523    }
2524    impl Lifecycle for LcModule {
2525        fn on_ready(_kit: &Kit<Ready>) -> Result<(), Self::Error> {
2526            LC_READY.fetch_add(1, Ordering::SeqCst);
2527            Ok(())
2528        }
2529        fn on_shutdown(_cap: &Arc<AtomicUsize>) {
2530            LC_SHUTDOWN.fetch_add(1, Ordering::SeqCst);
2531        }
2532    }
2533
2534    #[test]
2535    fn lifecycle_on_ready_called_during_build() {
2536        LC_READY.store(0, Ordering::SeqCst);
2537        let mut kit = Kit::new();
2538        kit.register::<LcModule>().unwrap();
2539        kit.register_lifecycle::<LcModule>();
2540        let _built = kit.build().unwrap();
2541        assert_eq!(
2542            LC_READY.load(Ordering::SeqCst),
2543            1,
2544            "on_ready should be called once"
2545        );
2546    }
2547
2548    #[test]
2549    fn lifecycle_shutdown_called_in_reverse_order() {
2550        LC_SHUTDOWN.store(0, Ordering::SeqCst);
2551        let mut kit = Kit::new();
2552        kit.register::<LcModule>().unwrap();
2553        kit.register_lifecycle::<LcModule>();
2554        let built = kit.build().unwrap();
2555        built.shutdown();
2556        assert_eq!(
2557            LC_SHUTDOWN.load(Ordering::SeqCst),
2558            1,
2559            "on_shutdown should be called once"
2560        );
2561    }
2562
2563    #[test]
2564    fn lifecycle_on_ready_failure_propagates() {
2565        struct FailReadyModule;
2566        impl ModuleMeta for FailReadyModule {
2567            const NAME: &'static str = "fail-ready";
2568            fn dependencies() -> &'static [(&'static str, TypeId)] {
2569                &[]
2570            }
2571        }
2572        impl AutoBuilder for FailReadyModule {
2573            type Capability = Arc<()>;
2574            type Error = TraitKitError;
2575            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2576                Ok(Arc::new(()))
2577            }
2578        }
2579        impl Lifecycle for FailReadyModule {
2580            fn on_ready(_kit: &Kit<Ready>) -> Result<(), TraitKitError> {
2581                Err(TraitKitError::BuildFailed {
2582                    context: "on_ready".into(),
2583                    source: Box::new(std::io::Error::other("intentional failure")),
2584                })
2585            }
2586        }
2587
2588        let mut kit = Kit::new();
2589        kit.register::<FailReadyModule>().unwrap();
2590        kit.register_lifecycle::<FailReadyModule>();
2591        let result = kit.build();
2592        assert!(result.is_err(), "build should fail when on_ready fails");
2593        let err = result.unwrap_err();
2594        assert!(matches!(err, TraitKitError::LifecycleFailed { .. }));
2595    }
2596}
2597
2598#[cfg(all(test, feature = "health"))]
2599mod health_tests {
2600    use super::*;
2601    use crate::core::ModuleMeta;
2602    use crate::core::health::{HealthCheck, HealthStatus};
2603    use std::sync::Arc;
2604
2605    #[derive(Debug, Clone)]
2606    struct HcCap {
2607        val: i32,
2608    }
2609
2610    struct HcModule;
2611    impl ModuleMeta for HcModule {
2612        const NAME: &'static str = "hc-module";
2613        fn dependencies() -> &'static [(&'static str, TypeId)] {
2614            &[]
2615        }
2616    }
2617    impl AutoBuilder for HcModule {
2618        type Capability = Arc<HcCap>;
2619        type Error = TraitKitError;
2620        fn build(_kit: &Kit) -> Result<Arc<HcCap>, TraitKitError> {
2621            Ok(Arc::new(HcCap { val: 42 }))
2622        }
2623    }
2624    impl HealthCheck for HcModule {
2625        fn check(cap: &Arc<HcCap>) -> HealthStatus {
2626            if cap.val > 0 {
2627                HealthStatus::Healthy
2628            } else {
2629                HealthStatus::Unhealthy {
2630                    detail: "zero".into(),
2631                }
2632            }
2633        }
2634    }
2635
2636    #[test]
2637    fn health_check_registered_and_queryable() {
2638        let mut kit = Kit::new();
2639        kit.register::<HcModule>().unwrap();
2640        kit.register_health_check::<HcModule>();
2641        let built = kit.build().unwrap();
2642        let status = built.health_check::<HcModule>().unwrap();
2643        assert_eq!(status, HealthStatus::Healthy);
2644    }
2645
2646    #[test]
2647    fn health_report_returns_all_checkers() {
2648        let mut kit = Kit::new();
2649        kit.register::<HcModule>().unwrap();
2650        kit.register_health_check::<HcModule>();
2651        let built = kit.build().unwrap();
2652        let report = built.health_report();
2653        assert_eq!(report.len(), 1);
2654        assert_eq!(report[0].0, "hc-module");
2655        assert_eq!(report[0].1, HealthStatus::Healthy);
2656    }
2657
2658    #[test]
2659    fn health_check_unregistered_returns_error() {
2660        let mut kit = Kit::new();
2661        kit.register::<HcModule>().unwrap();
2662        let built = kit.build().unwrap();
2663        let err = built.health_check::<HcModule>().unwrap_err();
2664        assert!(matches!(err, TraitKitError::MissingConfig { .. }));
2665    }
2666
2667    #[test]
2668    fn health_check_unhealthy_for_zero_value() {
2669        struct ZeroHcModule;
2670        impl ModuleMeta for ZeroHcModule {
2671            const NAME: &'static str = "zero-hc";
2672            fn dependencies() -> &'static [(&'static str, TypeId)] {
2673                &[]
2674            }
2675        }
2676        impl AutoBuilder for ZeroHcModule {
2677            type Capability = Arc<HcCap>;
2678            type Error = TraitKitError;
2679            fn build(_kit: &Kit) -> Result<Arc<HcCap>, TraitKitError> {
2680                Ok(Arc::new(HcCap { val: 0 }))
2681            }
2682        }
2683        impl HealthCheck for ZeroHcModule {
2684            fn check(cap: &Arc<HcCap>) -> HealthStatus {
2685                if cap.val > 0 {
2686                    HealthStatus::Healthy
2687                } else {
2688                    HealthStatus::Unhealthy {
2689                        detail: "zero".into(),
2690                    }
2691                }
2692            }
2693        }
2694
2695        let mut kit = Kit::new();
2696        kit.register::<ZeroHcModule>().unwrap();
2697        kit.register_health_check::<ZeroHcModule>();
2698        let built = kit.build().unwrap();
2699        let status = built.health_check::<ZeroHcModule>().unwrap();
2700        assert!(matches!(status, HealthStatus::Unhealthy { .. }));
2701    }
2702}
2703
2704#[cfg(all(test, feature = "observer"))]
2705mod observability_tests {
2706    use super::*;
2707    use crate::core::ModuleMeta;
2708    use crate::core::observer::BuildObserver;
2709    use std::sync::Arc;
2710    use std::sync::atomic::{AtomicUsize, Ordering};
2711    use std::time::Duration;
2712
2713    struct CountingObs {
2714        start: Arc<AtomicUsize>,
2715        built: Arc<AtomicUsize>,
2716    }
2717    impl BuildObserver for CountingObs {
2718        fn on_module_start(&self, _: &'static str) {
2719            self.start.fetch_add(1, Ordering::SeqCst);
2720        }
2721        fn on_module_built(&self, _: &'static str, _: Duration) {
2722            self.built.fetch_add(1, Ordering::SeqCst);
2723        }
2724    }
2725
2726    struct ObsModule;
2727    impl ModuleMeta for ObsModule {
2728        const NAME: &'static str = "obs-module";
2729        fn dependencies() -> &'static [(&'static str, TypeId)] {
2730            &[]
2731        }
2732    }
2733    impl AutoBuilder for ObsModule {
2734        type Capability = Arc<()>;
2735        type Error = TraitKitError;
2736        fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2737            Ok(Arc::new(()))
2738        }
2739    }
2740
2741    #[test]
2742    fn observer_callbacks_fired_during_build() {
2743        let start = Arc::new(AtomicUsize::new(0));
2744        let built = Arc::new(AtomicUsize::new(0));
2745        let obs = Arc::new(CountingObs {
2746            start: Arc::clone(&start),
2747            built: Arc::clone(&built),
2748        });
2749        let mut kit = Kit::new();
2750        kit.with_observer(obs);
2751        kit.register::<ObsModule>().unwrap();
2752        kit.build().unwrap();
2753        assert_eq!(
2754            start.load(Ordering::SeqCst),
2755            1,
2756            "on_module_start should fire"
2757        );
2758        assert_eq!(
2759            built.load(Ordering::SeqCst),
2760            1,
2761            "on_module_built should fire"
2762        );
2763    }
2764
2765    #[test]
2766    fn observer_on_build_error_called_on_failure() {
2767        struct FailObs {
2768            errors: Arc<AtomicUsize>,
2769        }
2770        impl BuildObserver for FailObs {
2771            fn on_build_error(&self, _: &'static str, _: &TraitKitError) {
2772                self.errors.fetch_add(1, Ordering::SeqCst);
2773            }
2774        }
2775
2776        struct FailBuildModule;
2777        impl ModuleMeta for FailBuildModule {
2778            const NAME: &'static str = "fail-build";
2779            fn dependencies() -> &'static [(&'static str, TypeId)] {
2780                &[]
2781            }
2782        }
2783        impl AutoBuilder for FailBuildModule {
2784            type Capability = Arc<()>;
2785            type Error = TraitKitError;
2786            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2787                Err(TraitKitError::BuildFailed {
2788                    context: "intentional".into(),
2789                    source: Box::new(std::io::Error::other("test failure")),
2790                })
2791            }
2792        }
2793
2794        let errors = Arc::new(AtomicUsize::new(0));
2795        let obs = Arc::new(FailObs {
2796            errors: Arc::clone(&errors),
2797        });
2798        let mut kit = Kit::new();
2799        kit.with_observer(obs);
2800        kit.register::<FailBuildModule>().unwrap();
2801        let result = kit.build();
2802        assert!(result.is_err(), "build should fail");
2803        assert_eq!(
2804            errors.load(Ordering::SeqCst),
2805            1,
2806            "on_build_error should fire once"
2807        );
2808    }
2809}
2810
2811#[cfg(test)]
2812mod factory_tests {
2813    use super::*;
2814    use crate::core::ModuleMeta;
2815    use std::sync::Arc;
2816    use std::sync::atomic::{AtomicUsize, Ordering};
2817
2818    static FACTORY_COUNT: AtomicUsize = AtomicUsize::new(0);
2819
2820    struct FactoryModule;
2821    impl ModuleMeta for FactoryModule {
2822        const NAME: &'static str = "factory-module";
2823        fn dependencies() -> &'static [(&'static str, TypeId)] {
2824            &[]
2825        }
2826    }
2827    impl AutoBuilder for FactoryModule {
2828        type Capability = Arc<AtomicUsize>;
2829        type Error = TraitKitError;
2830        fn build(_kit: &Kit) -> Result<Arc<AtomicUsize>, TraitKitError> {
2831            let n = FACTORY_COUNT.fetch_add(1, Ordering::SeqCst);
2832            Ok(Arc::new(AtomicUsize::new(n)))
2833        }
2834    }
2835
2836    #[test]
2837    fn factory_creates_new_instance_each_call() {
2838        FACTORY_COUNT.store(0, Ordering::SeqCst);
2839        let mut kit = Kit::new();
2840        kit.register::<FactoryModule>().unwrap();
2841        let built = kit.build().unwrap();
2842        let factory = built.factory::<FactoryModule>();
2843        let cap1 = factory().unwrap();
2844        let cap2 = factory().unwrap();
2845        // Each call invokes build() — counter increments
2846        assert_ne!(
2847            cap1.load(Ordering::SeqCst),
2848            cap2.load(Ordering::SeqCst),
2849            "factory should produce different instances"
2850        );
2851    }
2852}
2853
2854#[cfg(all(test, feature = "scope"))]
2855mod scope_tests {
2856    use super::*;
2857    use crate::core::ModuleMeta;
2858    use std::sync::Arc;
2859
2860    struct ScopeMockModule;
2861    impl ModuleMeta for ScopeMockModule {
2862        const NAME: &'static str = "scope-mock";
2863        fn dependencies() -> &'static [(&'static str, TypeId)] {
2864            &[]
2865        }
2866    }
2867    impl AutoBuilder for ScopeMockModule {
2868        type Capability = Arc<()>;
2869        type Error = TraitKitError;
2870        fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2871            Ok(Arc::new(()))
2872        }
2873    }
2874
2875    #[test]
2876    fn create_scope_returns_empty_scope() {
2877        let mut kit = Kit::new();
2878        kit.register::<ScopeMockModule>().unwrap();
2879        let built = kit.build().unwrap();
2880        let scope = built.create_scope();
2881        assert!(!scope.contains::<ScopeMockModule>());
2882    }
2883}
2884
2885#[cfg(test)]
2886mod conditional_tests {
2887    use super::*;
2888    use crate::core::ModuleMeta;
2889    use std::sync::Arc;
2890
2891    struct CondMockModule;
2892    impl ModuleMeta for CondMockModule {
2893        const NAME: &'static str = "cond-mock";
2894        fn dependencies() -> &'static [(&'static str, TypeId)] {
2895            &[]
2896        }
2897    }
2898    impl AutoBuilder for CondMockModule {
2899        type Capability = Arc<()>;
2900        type Error = TraitKitError;
2901        fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
2902            Ok(Arc::new(()))
2903        }
2904    }
2905
2906    #[test]
2907    fn register_if_true_registers_module() {
2908        let mut kit = Kit::new();
2909        let registered = kit.register_if::<CondMockModule>(|_| true).unwrap();
2910        assert!(registered);
2911        let built = kit.build().unwrap();
2912        assert!(built.contains::<CondMockModule>());
2913    }
2914
2915    #[test]
2916    fn register_if_false_skips_module() {
2917        let mut kit = Kit::new();
2918        let registered = kit.register_if::<CondMockModule>(|_| false).unwrap();
2919        assert!(!registered);
2920        let built = kit.build().unwrap();
2921        assert!(!built.contains::<CondMockModule>());
2922    }
2923}
2924
2925#[cfg(all(test, feature = "decorator"))]
2926mod decorator_tests {
2927    use super::*;
2928    use crate::core::ModuleMeta;
2929    use std::sync::Arc;
2930
2931    #[derive(Debug, Clone)]
2932    struct DecCap {
2933        val: String,
2934    }
2935
2936    struct DecModule;
2937    impl ModuleMeta for DecModule {
2938        const NAME: &'static str = "dec-module";
2939        fn dependencies() -> &'static [(&'static str, TypeId)] {
2940            &[]
2941        }
2942    }
2943    impl AutoBuilder for DecModule {
2944        type Capability = Arc<DecCap>;
2945        type Error = TraitKitError;
2946        fn build(_kit: &Kit) -> Result<Arc<DecCap>, TraitKitError> {
2947            Ok(Arc::new(DecCap {
2948                val: "original".into(),
2949            }))
2950        }
2951    }
2952
2953    #[test]
2954    fn decorate_registers_decorator() {
2955        let mut kit = Kit::new();
2956        kit.register_lazy::<DecModule>().unwrap();
2957        kit.decorate::<DecModule>(|cap| {
2958            Arc::new(DecCap {
2959                val: format!("{}+decorated", cap.val),
2960            })
2961        });
2962        // Decorator is applied during lazy require()
2963        let built = kit.build().unwrap();
2964        let cap = built.require::<DecModule>().unwrap();
2965        assert_eq!(cap.val, "original+decorated");
2966    }
2967}
2968
2969#[cfg(all(test, feature = "encryption"))]
2970mod encryption_tests {
2971    use super::*;
2972    use crate::kit::ModuleConfig;
2973    use serde::{Deserialize, Serialize};
2974
2975    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2976    struct SecretConfig {
2977        api_key: String,
2978    }
2979
2980    impl ModuleConfig for SecretConfig {
2981        const PATH: &'static str = "test.secret";
2982        fn default_value() -> Self {
2983            Self {
2984                api_key: "default".into(),
2985            }
2986        }
2987    }
2988
2989    #[test]
2990    fn set_and_get_encrypted_roundtrip() {
2991        let kit = Kit::new();
2992        let master_key = [0x42u8; 32];
2993        let config = SecretConfig {
2994            api_key: "super-secret".into(),
2995        };
2996        kit.set_encrypted(&config, &master_key).unwrap();
2997        assert!(kit.contains_encrypted::<SecretConfig>());
2998        let built = kit.build().unwrap();
2999        let decrypted: SecretConfig = built.get_encrypted(&master_key).unwrap();
3000        assert_eq!(decrypted, config);
3001    }
3002
3003    #[test]
3004    fn contains_encrypted_false_for_missing() {
3005        let kit = Kit::new();
3006        assert!(!kit.contains_encrypted::<SecretConfig>());
3007    }
3008
3009    #[test]
3010    fn get_encrypted_missing_returns_error() {
3011        let kit = Kit::new();
3012        let built = kit.build().unwrap();
3013        let master_key = [0x42u8; 32];
3014        let err = built
3015            .get_encrypted::<SecretConfig>(&master_key)
3016            .unwrap_err();
3017        assert!(matches!(err, TraitKitError::MissingConfig { .. }));
3018    }
3019
3020    #[test]
3021    fn secret_config_default_value() {
3022        let default = SecretConfig::default_value();
3023        assert_eq!(default.api_key, "default");
3024    }
3025
3026    #[test]
3027    fn get_encrypted_wrong_key_returns_error() {
3028        let kit = Kit::new();
3029        let master_key = [0x42u8; 32];
3030        let config = SecretConfig {
3031            api_key: "secret".into(),
3032        };
3033        kit.set_encrypted(&config, &master_key).unwrap();
3034        let built = kit.build().unwrap();
3035        // Use a different key to trigger decryption failure
3036        let wrong_key = [0xFFu8; 32];
3037        let err = built.get_encrypted::<SecretConfig>(&wrong_key).unwrap_err();
3038        assert!(matches!(err, TraitKitError::BuildFailed { .. }));
3039    }
3040}
3041
3042// ─── Kit<Ready> surface tests ─────────────────────────────────────────────
3043
3044#[cfg(test)]
3045mod ready_tests {
3046    use super::*;
3047    use crate::core::ModuleMeta;
3048    use std::sync::Arc;
3049
3050    struct ReadyMockModule;
3051    impl ModuleMeta for ReadyMockModule {
3052        const NAME: &'static str = "ready-mock";
3053        fn dependencies() -> &'static [(&'static str, TypeId)] {
3054            &[]
3055        }
3056    }
3057    impl AutoBuilder for ReadyMockModule {
3058        type Capability = Arc<()>;
3059        type Error = TraitKitError;
3060        fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3061            Ok(Arc::new(()))
3062        }
3063    }
3064
3065    #[test]
3066    fn ready_optional_returns_none_for_unbuilt() {
3067        let kit = Kit::new();
3068        let built = kit.build().unwrap();
3069        assert!(built.optional::<ReadyMockModule>().is_none());
3070    }
3071
3072    #[test]
3073    fn ready_optional_returns_some_for_built() {
3074        let mut kit = Kit::new();
3075        kit.register::<ReadyMockModule>().unwrap();
3076        let built = kit.build().unwrap();
3077        assert!(built.optional::<ReadyMockModule>().is_some());
3078    }
3079
3080    #[test]
3081    fn ready_contains_returns_true_for_built() {
3082        let mut kit = Kit::new();
3083        kit.register::<ReadyMockModule>().unwrap();
3084        let built = kit.build().unwrap();
3085        assert!(built.contains::<ReadyMockModule>());
3086    }
3087
3088    #[test]
3089    fn ready_contains_returns_false_for_unbuilt() {
3090        let kit = Kit::new();
3091        let built = kit.build().unwrap();
3092        assert!(!built.contains::<ReadyMockModule>());
3093    }
3094
3095    #[test]
3096    fn ready_contains_config_returns_true() {
3097        let kit = Kit::new();
3098        kit.set_config(42i32);
3099        let built = kit.build().unwrap();
3100        assert!(built.contains_config::<i32>());
3101    }
3102
3103    #[test]
3104    fn ready_contains_config_returns_false() {
3105        let kit = Kit::new();
3106        let built = kit.build().unwrap();
3107        assert!(!built.contains_config::<u64>());
3108    }
3109
3110    #[test]
3111    fn debug_unbuilt_format() {
3112        let kit = Kit::new();
3113        let debug = format!("{kit:?}");
3114        assert!(debug.contains("Kit<Unbuilt>"));
3115        assert!(debug.contains("modules"));
3116    }
3117
3118    #[test]
3119    fn debug_ready_format() {
3120        let mut kit = Kit::new();
3121        kit.register::<ReadyMockModule>().unwrap();
3122        let built = kit.build().unwrap();
3123        let debug = format!("{built:?}");
3124        assert!(debug.contains("Kit<Ready>"));
3125        assert!(debug.contains("modules"));
3126    }
3127
3128    #[test]
3129    fn default_creates_empty_kit() {
3130        let kit = Kit::default();
3131        let built = kit.build().unwrap();
3132        assert_eq!(built.graph.entries().len(), 0);
3133    }
3134
3135    #[test]
3136    fn graph_dot_returns_valid_string() {
3137        let mut kit = Kit::new();
3138        kit.register::<ReadyMockModule>().unwrap();
3139        let built = kit.build().unwrap();
3140        let dot = built.graph_dot();
3141        assert!(dot.contains("digraph"));
3142    }
3143
3144    #[test]
3145    fn graph_mermaid_returns_valid_string() {
3146        let mut kit = Kit::new();
3147        kit.register::<ReadyMockModule>().unwrap();
3148        let built = kit.build().unwrap();
3149        let mermaid = built.graph_mermaid();
3150        assert!(mermaid.contains("graph TD"));
3151    }
3152
3153    #[test]
3154    fn config_missing_returns_error() {
3155        let kit = Kit::new();
3156        let built = kit.build().unwrap();
3157        let err = built.config::<i32>().unwrap_err();
3158        assert!(matches!(err, TraitKitError::MissingConfig { .. }));
3159    }
3160
3161    #[test]
3162    fn require_ref_returns_missing_for_unbuilt() {
3163        let kit = Kit::new();
3164        let built = kit.build().unwrap();
3165        let err = built.require_ref::<ReadyMockModule>().unwrap_err();
3166        assert!(matches!(err, TraitKitError::MissingCapability { .. }));
3167    }
3168
3169    #[test]
3170    fn build_missing_dependency_returns_error() {
3171        struct DepModule;
3172        impl ModuleMeta for DepModule {
3173            const NAME: &'static str = "dep";
3174            fn dependencies() -> &'static [(&'static str, TypeId)] {
3175                &[]
3176            }
3177        }
3178        impl AutoBuilder for DepModule {
3179            type Capability = Arc<()>;
3180            type Error = TraitKitError;
3181            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3182                Ok(Arc::new(()))
3183            }
3184        }
3185
3186        struct NeedsDepModule;
3187        impl ModuleMeta for NeedsDepModule {
3188            const NAME: &'static str = "needs-dep";
3189            fn dependencies() -> &'static [(&'static str, TypeId)] {
3190                static DEPS: &[(&str, TypeId)] = &[("dep", TypeId::of::<DepModule>())];
3191                DEPS
3192            }
3193        }
3194        impl AutoBuilder for NeedsDepModule {
3195            type Capability = Arc<()>;
3196            type Error = TraitKitError;
3197            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3198                Ok(Arc::new(()))
3199            }
3200        }
3201
3202        let mut kit = Kit::new();
3203        kit.register::<NeedsDepModule>().unwrap();
3204        // Don't register DepModule — should fail
3205        let result = kit.build();
3206        assert!(result.is_err());
3207        assert!(matches!(
3208            result.unwrap_err(),
3209            TraitKitError::DependencyMissing { .. }
3210        ));
3211    }
3212
3213    #[test]
3214    fn build_cycle_detected_returns_error() {
3215        struct CycleA;
3216        impl ModuleMeta for CycleA {
3217            const NAME: &'static str = "cycle-a";
3218            fn dependencies() -> &'static [(&'static str, TypeId)] {
3219                static DEPS: &[(&str, TypeId)] = &[("cycle-b", TypeId::of::<CycleB>())];
3220                DEPS
3221            }
3222        }
3223        impl AutoBuilder for CycleA {
3224            type Capability = Arc<()>;
3225            type Error = TraitKitError;
3226            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3227                Ok(Arc::new(()))
3228            }
3229        }
3230
3231        struct CycleB;
3232        impl ModuleMeta for CycleB {
3233            const NAME: &'static str = "cycle-b";
3234            fn dependencies() -> &'static [(&'static str, TypeId)] {
3235                static DEPS: &[(&str, TypeId)] = &[("cycle-a", TypeId::of::<CycleA>())];
3236                DEPS
3237            }
3238        }
3239        impl AutoBuilder for CycleB {
3240            type Capability = Arc<()>;
3241            type Error = TraitKitError;
3242            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3243                Ok(Arc::new(()))
3244            }
3245        }
3246
3247        let mut kit = Kit::new();
3248        kit.register::<CycleA>().unwrap();
3249        kit.register::<CycleB>().unwrap();
3250        let result = kit.build();
3251        assert!(result.is_err());
3252        assert!(matches!(
3253            result.unwrap_err(),
3254            TraitKitError::CycleDetected { .. }
3255        ));
3256    }
3257
3258    #[test]
3259    fn lazy_require_build_error() {
3260        struct LazyFailModule;
3261        impl ModuleMeta for LazyFailModule {
3262            const NAME: &'static str = "lazy-fail";
3263            fn dependencies() -> &'static [(&'static str, TypeId)] {
3264                &[]
3265            }
3266        }
3267        impl AutoBuilder for LazyFailModule {
3268            type Capability = Arc<()>;
3269            type Error = TraitKitError;
3270            fn build(_kit: &Kit) -> Result<Arc<()>, TraitKitError> {
3271                Err(TraitKitError::BuildFailed {
3272                    context: "lazy-fail".into(),
3273                    source: Box::new(std::io::Error::other("lazy fail")),
3274                })
3275            }
3276        }
3277
3278        let mut kit = Kit::new();
3279        kit.register_lazy::<LazyFailModule>().unwrap();
3280        let built = kit.build().unwrap();
3281        let err = built.require::<LazyFailModule>().unwrap_err();
3282        assert!(matches!(err, TraitKitError::BuildFailed { .. }));
3283    }
3284}
3285
3286// ─── Validation Tests ───────────────────────────────────────────────────────
3287
3288#[cfg(all(test, feature = "confers"))]
3289mod validation_tests {
3290    use super::*;
3291    use crate::kit::config::{Configurable, Validatable};
3292    use std::error::Error;
3293
3294    #[derive(Clone, Debug, PartialEq)]
3295    struct ValidConfig {
3296        port: u16,
3297    }
3298
3299    impl Configurable for ValidConfig {
3300        fn load() -> Result<Self, Box<dyn Error + Send>> {
3301            Ok(Self { port: 8080 })
3302        }
3303    }
3304
3305    impl Validatable for ValidConfig {
3306        fn validate(&self) -> Result<(), Vec<String>> {
3307            if self.port > 0 && self.port < 65535 {
3308                Ok(())
3309            } else {
3310                Err(vec!["port out of range".to_string()])
3311            }
3312        }
3313    }
3314
3315    #[derive(Clone, Debug, PartialEq)]
3316    struct InvalidConfig {
3317        port: u16,
3318    }
3319
3320    impl Configurable for InvalidConfig {
3321        fn load() -> Result<Self, Box<dyn Error + Send>> {
3322            Ok(Self { port: 0 })
3323        }
3324    }
3325
3326    impl Validatable for InvalidConfig {
3327        fn validate(&self) -> Result<(), Vec<String>> {
3328            Err(vec![
3329                "port must be > 0".to_string(),
3330                "port must be < 65535".to_string(),
3331            ])
3332        }
3333    }
3334
3335    #[test]
3336    fn load_and_validate_succeeds_with_valid_config() {
3337        let kit = Kit::new();
3338        kit.load_and_validate::<ValidConfig>()
3339            .expect("valid config should pass");
3340        let config: ValidConfig = kit.config().expect("config should be stored");
3341        assert_eq!(config.port, 8080);
3342    }
3343
3344    #[test]
3345    fn load_and_validate_fails_with_invalid_config() {
3346        let kit = Kit::new();
3347        let err = kit
3348            .load_and_validate::<InvalidConfig>()
3349            .expect_err("invalid config should fail");
3350        let msg = format!("{err}");
3351        assert!(
3352            msg.contains("port must be > 0"),
3353            "error should contain first validation error: {msg}"
3354        );
3355        assert!(
3356            msg.contains("port must be < 65535"),
3357            "error should contain second validation error: {msg}"
3358        );
3359    }
3360
3361    #[test]
3362    fn load_and_validate_does_not_store_on_failure() {
3363        let kit = Kit::new();
3364        let _ = kit.load_and_validate::<InvalidConfig>();
3365        let result: Result<InvalidConfig, _> = kit.config();
3366        assert!(result.is_err(), "invalid config should not be stored");
3367    }
3368
3369    #[test]
3370    fn load_and_validate_retry_after_failure() {
3371        let kit = Kit::new();
3372        let _ = kit.load_and_validate::<InvalidConfig>();
3373        // Now load a valid config of a different type
3374        kit.load_and_validate::<ValidConfig>()
3375            .expect("valid config should succeed after previous failure");
3376        let config: ValidConfig = kit.config().expect("valid config should be stored");
3377        assert_eq!(config.port, 8080);
3378    }
3379}
3380
3381// ─── Snapshot Tests ─────────────────────────────────────────────────────────
3382
3383#[cfg(all(test, feature = "confers"))]
3384mod snapshot_tests {
3385    use super::*;
3386    use crate::kit::config::Configurable;
3387    use std::error::Error;
3388
3389    #[derive(Clone, Debug, PartialEq)]
3390    struct SnapConfig {
3391        value: String,
3392    }
3393
3394    impl Configurable for SnapConfig {
3395        fn load() -> Result<Self, Box<dyn Error + Send>> {
3396            Ok(Self {
3397                value: "loaded".to_string(),
3398            })
3399        }
3400    }
3401
3402    #[test]
3403    fn snapshot_returns_true_when_config_exists() {
3404        let kit = Kit::new();
3405        kit.set_config(SnapConfig {
3406            value: "original".to_string(),
3407        });
3408        assert!(kit.snapshot_config::<SnapConfig>());
3409    }
3410
3411    #[test]
3412    fn snapshot_returns_false_when_config_missing() {
3413        let kit = Kit::new();
3414        assert!(!kit.snapshot_config::<SnapConfig>());
3415    }
3416
3417    #[test]
3418    fn restore_overwrites_current_config() {
3419        let kit = Kit::new();
3420        kit.set_config(SnapConfig {
3421            value: "original".to_string(),
3422        });
3423        kit.snapshot_config::<SnapConfig>();
3424        // Modify current config
3425        kit.set_config(SnapConfig {
3426            value: "modified".to_string(),
3427        });
3428        let current: SnapConfig = kit.config().unwrap();
3429        assert_eq!(current.value, "modified");
3430        // Restore from snapshot
3431        kit.restore_config::<SnapConfig>()
3432            .expect("restore should succeed");
3433        let restored: SnapConfig = kit.config().unwrap();
3434        assert_eq!(restored.value, "original");
3435    }
3436
3437    #[test]
3438    fn restore_returns_error_when_no_snapshot() {
3439        let kit = Kit::new();
3440        let err = kit
3441            .restore_config::<SnapConfig>()
3442            .expect_err("restore without snapshot should fail");
3443        assert!(matches!(err, TraitKitError::MissingConfig { .. }));
3444    }
3445
3446    #[test]
3447    fn has_snapshot_reflects_state() {
3448        let kit = Kit::new();
3449        assert!(!kit.has_snapshot::<SnapConfig>());
3450        kit.set_config(SnapConfig {
3451            value: "test".to_string(),
3452        });
3453        kit.snapshot_config::<SnapConfig>();
3454        assert!(kit.has_snapshot::<SnapConfig>());
3455    }
3456
3457    #[test]
3458    fn snapshot_overwrite_replaces_previous() {
3459        let kit = Kit::new();
3460        kit.set_config(SnapConfig {
3461            value: "v1".to_string(),
3462        });
3463        kit.snapshot_config::<SnapConfig>();
3464        kit.set_config(SnapConfig {
3465            value: "v2".to_string(),
3466        });
3467        kit.snapshot_config::<SnapConfig>();
3468        // Restore should get v2 (latest snapshot)
3469        kit.set_config(SnapConfig {
3470            value: "current".to_string(),
3471        });
3472        kit.restore_config::<SnapConfig>().unwrap();
3473        let restored: SnapConfig = kit.config().unwrap();
3474        assert_eq!(restored.value, "v2");
3475    }
3476}
3477
3478// ─── Reload Tests ───────────────────────────────────────────────────────────
3479
3480#[cfg(all(test, feature = "reload"))]
3481mod reload_tests {
3482    use super::*;
3483    use crate::kit::config::Configurable;
3484    use std::error::Error;
3485    use std::sync::Arc;
3486    use std::sync::atomic::{AtomicU32, Ordering};
3487
3488    #[derive(Clone, Debug, PartialEq)]
3489    struct ReloadConfig {
3490        version: u32,
3491    }
3492
3493    // 每次 load() 递增,证明 reload 真正重新加载而非复用缓存。
3494    static LOAD_COUNT: AtomicU32 = AtomicU32::new(0);
3495
3496    impl Configurable for ReloadConfig {
3497        fn load() -> Result<Self, Box<dyn Error + Send>> {
3498            let v = LOAD_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
3499            Ok(Self { version: v })
3500        }
3501    }
3502
3503    #[test]
3504    fn reload_config_updates_value_and_fires_subscribers() {
3505        let kit = Kit::new();
3506        let notified = Arc::new(AtomicU32::new(0));
3507        let notified_clone = Arc::clone(&notified);
3508        kit.subscribe::<ReloadConfig>(move || {
3509            notified_clone.fetch_add(1, Ordering::SeqCst);
3510        });
3511
3512        // 记录当前计数,断言 reload 后一定递增(对测试执行顺序无关)。
3513        let before = LOAD_COUNT.load(Ordering::SeqCst);
3514        kit.set_config(ReloadConfig { version: 0 });
3515        kit.reload_config::<ReloadConfig>()
3516            .expect("reload should succeed");
3517        let cfg: ReloadConfig = kit.config().expect("config present");
3518        assert!(cfg.version > before, "reload must call Configurable::load");
3519        assert_eq!(
3520            notified.load(Ordering::SeqCst),
3521            1,
3522            "subscriber must be invoked exactly once"
3523        );
3524    }
3525
3526    #[test]
3527    fn reload_config_fires_all_subscribers() {
3528        let kit = Kit::new();
3529        let count = Arc::new(AtomicU32::new(0));
3530        for _ in 0..3 {
3531            let count = Arc::clone(&count);
3532            kit.subscribe::<ReloadConfig>(move || {
3533                count.fetch_add(1, Ordering::SeqCst);
3534            });
3535        }
3536        kit.set_config(ReloadConfig { version: 0 });
3537        kit.reload_config::<ReloadConfig>().expect("reload ok");
3538        assert_eq!(
3539            count.load(Ordering::SeqCst),
3540            3,
3541            "all three subscribers must fire"
3542        );
3543    }
3544}
3545
3546// ─── Toggle Tests ───────────────────────────────────────────────────────────
3547
3548#[cfg(all(test, feature = "toggle"))]
3549mod toggle_tests {
3550    use super::*;
3551    use crate::core::ModuleMeta;
3552    use std::sync::Arc;
3553
3554    struct ToggleModule;
3555    impl ModuleMeta for ToggleModule {
3556        const NAME: &'static str = "toggle-mod";
3557        fn dependencies() -> &'static [(&'static str, TypeId)] {
3558            &[]
3559        }
3560    }
3561    impl AutoBuilder for ToggleModule {
3562        type Capability = Arc<String>;
3563        type Error = TraitKitError;
3564        fn build(_kit: &Kit) -> Result<Arc<String>, TraitKitError> {
3565            Ok(Arc::new("toggle-cap".to_string()))
3566        }
3567    }
3568
3569    #[test]
3570    fn enable_toggle_sets_value() {
3571        let kit = Kit::new();
3572        kit.enable_toggle("feature-a", true);
3573        assert!(kit.is_toggle_enabled("feature-a"));
3574        kit.enable_toggle("feature-a", false);
3575        assert!(!kit.is_toggle_enabled("feature-a"));
3576    }
3577
3578    #[test]
3579    fn is_toggle_enabled_returns_false_for_unknown() {
3580        let kit = Kit::new();
3581        assert!(!kit.is_toggle_enabled("nonexistent"));
3582    }
3583
3584    #[test]
3585    fn register_if_toggle_registers_when_enabled() {
3586        let mut kit = Kit::new();
3587        kit.enable_toggle("mod-x", true);
3588        let registered = kit
3589            .register_if_toggle::<ToggleModule>("mod-x")
3590            .expect("registration should succeed");
3591        assert!(registered);
3592    }
3593
3594    #[test]
3595    fn register_if_toggle_skips_when_disabled() {
3596        let mut kit = Kit::new();
3597        kit.enable_toggle("mod-x", false);
3598        let registered = kit
3599            .register_if_toggle::<ToggleModule>("mod-x")
3600            .expect("should return Ok(false)");
3601        assert!(!registered);
3602    }
3603
3604    #[test]
3605    fn register_if_toggle_returns_error_on_duplicate() {
3606        let mut kit = Kit::new();
3607        kit.enable_toggle("mod-x", true);
3608        kit.register_if_toggle::<ToggleModule>("mod-x")
3609            .expect("first registration");
3610        let err = kit
3611            .register_if_toggle::<ToggleModule>("mod-x")
3612            .expect_err("duplicate should fail");
3613        assert!(matches!(err, TraitKitError::AlreadyRegistered { .. }));
3614    }
3615
3616    #[test]
3617    fn toggle_state_survives_build() {
3618        let mut kit = Kit::new();
3619        kit.enable_toggle("persist", true);
3620        kit.register_if_toggle::<ToggleModule>("persist").unwrap();
3621        let ready = kit.build().unwrap();
3622        assert!(ready.is_toggle_enabled("persist"));
3623    }
3624
3625    #[test]
3626    fn toggle_enable_on_ready_state() {
3627        let mut kit = Kit::new();
3628        kit.register::<ToggleModule>().unwrap();
3629        let ready = kit.build().unwrap();
3630        ready.enable_toggle("runtime", true);
3631        assert!(ready.is_toggle_enabled("runtime"));
3632    }
3633
3634    #[test]
3635    fn toggle_disabled_capability_not_retrievable() {
3636        // 运行时停用 toggle 后:未注册的模块能力必须不可检索
3637        // (require 返回 MissingCapability,而非返回任何能力)。
3638        let mut kit = Kit::new();
3639        kit.enable_toggle("feature-gate", false);
3640        let registered = kit
3641            .register_if_toggle::<ToggleModule>("feature-gate")
3642            .expect("should return Ok(false)");
3643        assert!(!registered, "disabled toggle must not register the module");
3644
3645        let ready = kit.build().expect("build without the module succeeds");
3646        let err = ready.require::<ToggleModule>().unwrap_err();
3647        assert!(matches!(
3648            err,
3649            TraitKitError::MissingCapability { ref key } if key == "toggle-mod"
3650        ));
3651        assert!(!ready.contains::<ToggleModule>());
3652        assert!(ready.optional::<ToggleModule>().is_none());
3653    }
3654}
3655
3656// ─── Interpolation Tests ────────────────────────────────────────────────────
3657
3658#[cfg(all(test, feature = "confers"))]
3659mod interpolation_tests {
3660    use crate::kit::config::interpolate_json_value;
3661    use std::collections::HashMap;
3662
3663    #[test]
3664    fn basic_var_replacement() {
3665        let mut value = serde_json::json!("${HOST}");
3666        let mut vars = HashMap::new();
3667        vars.insert("HOST".to_string(), "localhost".to_string());
3668        interpolate_json_value(&mut value, &vars);
3669        assert_eq!(value, serde_json::json!("localhost"));
3670    }
3671
3672    #[test]
3673    fn default_value_when_var_missing() {
3674        let mut value = serde_json::json!("${HOST:-127.0.0.1}");
3675        let vars = HashMap::new();
3676        interpolate_json_value(&mut value, &vars);
3677        assert_eq!(value, serde_json::json!("127.0.0.1"));
3678    }
3679
3680    #[test]
3681    fn default_value_ignored_when_var_present() {
3682        let mut value = serde_json::json!("${HOST:-127.0.0.1}");
3683        let mut vars = HashMap::new();
3684        vars.insert("HOST".to_string(), "10.0.0.1".to_string());
3685        interpolate_json_value(&mut value, &vars);
3686        assert_eq!(value, serde_json::json!("10.0.0.1"));
3687    }
3688
3689    #[test]
3690    fn no_match_preserved() {
3691        let mut value = serde_json::json!("${UNKNOWN}");
3692        let vars = HashMap::new();
3693        interpolate_json_value(&mut value, &vars);
3694        assert_eq!(value, serde_json::json!("${UNKNOWN}"));
3695    }
3696
3697    #[test]
3698    fn nested_object_replacement() {
3699        let mut value = serde_json::json!({
3700            "db": {
3701                "host": "${DB_HOST}",
3702                "port": 5432
3703            }
3704        });
3705        let mut vars = HashMap::new();
3706        vars.insert("DB_HOST".to_string(), "db.example.com".to_string());
3707        interpolate_json_value(&mut value, &vars);
3708        assert_eq!(value["db"]["host"], serde_json::json!("db.example.com"));
3709        // Non-string values untouched
3710        assert_eq!(value["db"]["port"], serde_json::json!(5432));
3711    }
3712
3713    #[test]
3714    fn array_string_elements_replaced() {
3715        let mut value = serde_json::json!(["${A}", "${B}", 42]);
3716        let mut vars = HashMap::new();
3717        vars.insert("A".to_string(), "alpha".to_string());
3718        vars.insert("B".to_string(), "beta".to_string());
3719        interpolate_json_value(&mut value, &vars);
3720        assert_eq!(value[0], serde_json::json!("alpha"));
3721        assert_eq!(value[1], serde_json::json!("beta"));
3722        assert_eq!(value[2], serde_json::json!(42));
3723    }
3724
3725    #[test]
3726    fn non_string_values_untouched() {
3727        let mut value = serde_json::json!({
3728            "num": 42,
3729            "bool": true,
3730            "null": null
3731        });
3732        let vars = HashMap::new();
3733        interpolate_json_value(&mut value, &vars);
3734        assert_eq!(value["num"], serde_json::json!(42));
3735        assert_eq!(value["bool"], serde_json::json!(true));
3736        assert_eq!(value["null"], serde_json::json!(null));
3737    }
3738
3739    #[test]
3740    fn object_keys_not_replaced() {
3741        let mut value = serde_json::json!({"${KEY}": "value"});
3742        let vars = HashMap::new();
3743        interpolate_json_value(&mut value, &vars);
3744        // Key should remain as "${KEY}", not be replaced
3745        assert!(value.as_object().unwrap().contains_key("${KEY}"));
3746    }
3747
3748    #[test]
3749    fn multiple_vars_in_one_string() {
3750        let mut value = serde_json::json!("${HOST}:${PORT}");
3751        let mut vars = HashMap::new();
3752        vars.insert("HOST".to_string(), "localhost".to_string());
3753        vars.insert("PORT".to_string(), "8080".to_string());
3754        interpolate_json_value(&mut value, &vars);
3755        assert_eq!(value, serde_json::json!("localhost:8080"));
3756    }
3757}