selfware 0.6.4

Your personal AI workshop — software you own, software that lasts
Documentation
//! Configuration provenance tracking.
//!
//! Each effective config value records where it came from — the default
//! value, a TOML file, an environment variable, a profile section, a CLI
//! argument, or auto-configuration.  The `ConfigSources` map stores the
//! provenance for individual top-level fields so `selfware config show`
//! can annotate each effective value.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

/// Where a particular config value originated.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConfigSource {
    /// Hard-coded default in the source.
    Default,
    /// Loaded from a TOML configuration file.
    ConfigFile(PathBuf),
    /// Set by an environment variable (name held inside, e.g. `SELFWARE_MODEL`).
    EnvVar(String),
    /// Inherited from a named model profile (e.g. `qwen3.6-*`).
    Profile(String),
    /// Set by a CLI argument (flag name held inside, e.g. `--model`).
    CliArg(String),
    /// Generated by auto-calibration / unpack.
    AutoConfig,
}

impl ConfigSource {
    /// Short human label suitable for `selfware config show`.
    pub fn label(&self) -> String {
        match self {
            ConfigSource::Default => "default".to_string(),
            ConfigSource::ConfigFile(path) => path.display().to_string(),
            ConfigSource::EnvVar(name) => format!("{} env", name),
            ConfigSource::Profile(name) => format!("profile: {}", name),
            ConfigSource::CliArg(flag) => format!("cli: {}", flag),
            ConfigSource::AutoConfig => "auto-config".to_string(),
        }
    }
}

/// Map of dotted field name → source. Stored alongside the live `Config`
/// so callers can ask `config.source_of("endpoint")`.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ConfigSources {
    inner: HashMap<String, ConfigSource>,
}

impl ConfigSources {
    pub fn new() -> Self {
        Self::default()
    }

    /// Record (or replace) the source for a dotted field name.
    pub fn set(&mut self, key: impl Into<String>, source: ConfigSource) {
        self.inner.insert(key.into(), source);
    }

    /// Look up the recorded source for a dotted field name.
    pub fn get(&self, key: &str) -> Option<&ConfigSource> {
        self.inner.get(key)
    }

    /// Iterator over (key, source) pairs in insertion-stable sorted order.
    pub fn iter(&self) -> impl Iterator<Item = (&String, &ConfigSource)> {
        let mut keys: Vec<&String> = self.inner.keys().collect();
        keys.sort();
        keys.into_iter().map(move |k| (k, &self.inner[k]))
    }

    pub fn len(&self) -> usize {
        self.inner.len()
    }

    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }
}

#[cfg(test)]
#[path = "../../tests/unit/config/provenance/provenance_test.rs"]
mod tests;