use serde::Serialize;
use serde_json::{Value, json};
use crate::config::merge::{
ConfigValueSource, MESH_ENABLED_KEY, PACK_DEFAULT_MAX_TOKENS_KEY, PACK_MMR_LAMBDA_KEY,
SEARCH_DEFAULT_SPEED_KEY, SEARCH_GRAPH_WEIGHT_KEY, SEARCH_LEXICAL_WEIGHT_KEY,
SEARCH_RERANK_KEY, SEARCH_RERANK_TOP_K_KEY, SEARCH_SEMANTIC_WEIGHT_KEY,
STORAGE_DATABASE_PATH_KEY,
};
pub const CONFIG_EXPLAIN_SCHEMA_V1: &str = "ee.config_explain.v1";
pub const CONFIG_LINT_SEVERITY: &str = "advisory";
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfigRuntimeStatus {
Active,
ForwardLooking,
Inert,
}
impl ConfigRuntimeStatus {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Active => "active",
Self::ForwardLooking => "forward_looking",
Self::Inert => "inert",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ConfigKnob {
pub key: &'static str,
pub category: &'static str,
pub effect: &'static str,
pub valid_range: &'static str,
pub status: ConfigRuntimeStatus,
pub caveat: Option<&'static str>,
}
#[must_use]
pub fn config_knobs() -> &'static [ConfigKnob] {
&[
ConfigKnob {
key: MESH_ENABLED_KEY,
category: "mesh",
effect: "Enables the optional machine-to-machine memory mesh; off by default and never required for local operation.",
valid_range: "true | false",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: PACK_DEFAULT_MAX_TOKENS_KEY,
category: "pack",
effect: "Default token budget for `ee pack` when --max-tokens is not given.",
valid_range: "positive integer (tokens)",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: PACK_MMR_LAMBDA_KEY,
category: "pack",
effect: "MMR diversity/relevance tradeoff during pack selection: higher favors relevance, lower favors diversity.",
valid_range: "0.0 ..= 1.0",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: SEARCH_DEFAULT_SPEED_KEY,
category: "search",
effect: "Default latency/quality tradeoff for search; maps to the frankensearch embedder stack tier.",
valid_range: "fast | balanced | thorough",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: SEARCH_GRAPH_WEIGHT_KEY,
category: "search",
effect: "Fusion weight applied to graph-proximity signal when combining ranked result lists.",
valid_range: "0.0 ..= 1.0",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: SEARCH_LEXICAL_WEIGHT_KEY,
category: "search",
effect: "Fusion weight applied to the lexical (BM25/FTS) tier when combining ranked result lists.",
valid_range: "0.0 ..= 1.0",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: SEARCH_RERANK_KEY,
category: "search",
effect: "Controls whether the local reranker is auto-used when an available model is registered, or fully disabled before model lookup.",
valid_range: "auto | off",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: SEARCH_RERANK_TOP_K_KEY,
category: "search",
effect: "Candidate pool size collected for reranking before truncating to the requested search limit.",
valid_range: "positive integer",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
ConfigKnob {
key: SEARCH_SEMANTIC_WEIGHT_KEY,
category: "search",
effect: "Intended fusion weight for the vector (semantic) tier when combining ranked result lists.",
valid_range: "0.0 ..= 1.0",
status: ConfigRuntimeStatus::Active,
caveat: Some(
"Does not imply neural semantic search by itself: this weights the active vector tier, which may be a neural model or the deterministic hash fallback. Check `ee index status` for the active embedding mode (see ADR 0070 and the bundled-embeddings work, bd-1et0v).",
),
},
ConfigKnob {
key: STORAGE_DATABASE_PATH_KEY,
category: "storage",
effect: "Filesystem path to the durable ee memory database (the source of truth).",
valid_range: "filesystem path",
status: ConfigRuntimeStatus::Active,
caveat: None,
},
]
}
#[must_use]
pub fn knob_for_key(key: &str) -> Option<&'static ConfigKnob> {
config_knobs().iter().find(|knob| knob.key == key)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigExplanation {
pub knob: ConfigKnob,
pub effective_value: Option<String>,
pub source_layer: &'static str,
pub lint_findings: Vec<ConfigLintFinding>,
}
impl ConfigExplanation {
#[must_use]
pub fn data_json(&self) -> Value {
json!({
"schema": CONFIG_EXPLAIN_SCHEMA_V1,
"key": self.knob.key,
"category": self.knob.category,
"effect": self.knob.effect,
"validRange": self.knob.valid_range,
"status": self.knob.status.as_str(),
"caveat": self.knob.caveat,
"effectiveValue": self.effective_value,
"sourceLayer": self.source_layer,
"lintFindings": self
.lint_findings
.iter()
.map(ConfigLintFinding::data_json)
.collect::<Vec<_>>(),
})
}
#[must_use]
pub fn with_lint_findings(mut self, findings: &[ConfigLintFinding]) -> Self {
self.lint_findings = findings
.iter()
.filter(|finding| finding.key == self.knob.key)
.cloned()
.collect();
self
}
}
#[must_use]
pub fn explain(
key: &str,
effective_value: Option<String>,
source: Option<ConfigValueSource>,
) -> Option<ConfigExplanation> {
knob_for_key(key).map(|knob| ConfigExplanation {
knob: *knob,
effective_value,
source_layer: source.map_or("unknown", ConfigValueSource::as_str),
lint_findings: Vec::new(),
})
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfigLintFinding {
pub code: &'static str,
pub key: String,
pub message: String,
pub severity: &'static str,
}
impl ConfigLintFinding {
fn advisory(code: &'static str, key: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code,
key: key.into(),
message: message.into(),
severity: CONFIG_LINT_SEVERITY,
}
}
#[must_use]
pub fn data_json(&self) -> Value {
json!({
"code": self.code,
"key": self.key,
"message": self.message,
"severity": self.severity,
})
}
}
pub const LINT_WEIGHT_WITHOUT_NEURAL_TIER: &str = "config_semantic_weight_without_neural_tier";
pub const LINT_EMBEDDING_MODEL_PATH_MISSING: &str = "config_embedding_model_path_missing";
pub const LINT_UNKNOWN_ENV_VAR: &str = "config_unknown_env_var";
pub const LINT_CONTRADICTORY_LEXICAL_MODE: &str = "config_contradictory_lexical_mode";
#[must_use]
pub fn lint_semantic_weight_without_neural_tier(
semantic_weight: Option<f64>,
neural_tier_active: bool,
) -> Option<ConfigLintFinding> {
match semantic_weight {
Some(weight) if !neural_tier_active => Some(ConfigLintFinding::advisory(
LINT_WEIGHT_WITHOUT_NEURAL_TIER,
SEARCH_SEMANTIC_WEIGHT_KEY,
format!(
"search.semantic_weight={weight} is set, but the active vector tier is deterministic hash, not neural — this does not enable neural semantic search. Check `ee index status` for the active embedding mode."
),
)),
_ => None,
}
}
#[must_use]
pub fn lint_embedding_model_path_missing(
key: &str,
configured_path: Option<&str>,
path_exists: bool,
) -> Option<ConfigLintFinding> {
match configured_path {
Some(path) if !path_exists => Some(ConfigLintFinding::advisory(
LINT_EMBEDDING_MODEL_PATH_MISSING,
key,
format!("configured embedding model path does not exist: {path}"),
)),
_ => None,
}
}
#[must_use]
pub fn lint_unknown_env_var(var_name: &str, recognized: bool) -> Option<ConfigLintFinding> {
if recognized {
None
} else {
Some(ConfigLintFinding::advisory(
LINT_UNKNOWN_ENV_VAR,
var_name,
format!(
"environment variable `{var_name}` is set but `ee` does not consume it; it has no effect. See `ee capabilities --json` data.envOverrides[] for the variables ee honors."
),
))
}
}
#[must_use]
pub fn lint_contradictory_lexical_mode(
mode: Option<&str>,
semantic_weight: Option<f64>,
) -> Option<ConfigLintFinding> {
match (mode, semantic_weight) {
(Some("lexical"), Some(weight)) => Some(ConfigLintFinding::advisory(
LINT_CONTRADICTORY_LEXICAL_MODE,
SEARCH_SEMANTIC_WEIGHT_KEY,
format!(
"search mode is `lexical`, so search.semantic_weight={weight} has no effect. Set mode to `hybrid` to use the vector tier."
),
)),
_ => None,
}
}
#[derive(Clone, Debug, Default)]
pub struct ConfigLintFacts {
pub semantic_weight: Option<f64>,
pub neural_tier_active: bool,
pub search_mode: Option<String>,
pub embedding_model_path: Option<(String, String, bool)>,
pub env_vars: Vec<(String, bool)>,
}
#[must_use]
pub fn run_config_lint(facts: &ConfigLintFacts) -> Vec<ConfigLintFinding> {
let mut findings = Vec::new();
if let Some(finding) =
lint_semantic_weight_without_neural_tier(facts.semantic_weight, facts.neural_tier_active)
{
findings.push(finding);
}
if let Some(finding) =
lint_contradictory_lexical_mode(facts.search_mode.as_deref(), facts.semantic_weight)
{
findings.push(finding);
}
if let Some((key, path, exists)) = &facts.embedding_model_path {
if let Some(finding) = lint_embedding_model_path_missing(key, Some(path), *exists) {
findings.push(finding);
}
}
for (name, recognized) in &facts.env_vars {
if let Some(finding) = lint_unknown_env_var(name, *recognized) {
findings.push(finding);
}
}
findings.sort_by(|a, b| a.code.cmp(b.code).then_with(|| a.key.cmp(&b.key)));
findings
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_is_sorted_by_key_for_stable_output() {
let keys: Vec<&str> = config_knobs().iter().map(|knob| knob.key).collect();
let mut sorted = keys.clone();
sorted.sort_unstable();
assert_eq!(keys, sorted, "config knob registry must stay sorted by key");
}
#[test]
fn semantic_weight_knob_is_honest_about_neural() {
let knob = knob_for_key(SEARCH_SEMANTIC_WEIGHT_KEY).expect("semantic_weight is covered");
assert_eq!(knob.status, ConfigRuntimeStatus::Active);
let caveat = knob.caveat.expect("semantic_weight must carry a caveat");
assert!(
caveat.contains("vector") && caveat.contains("neural"),
"caveat must explain the vector-vs-neural truth"
);
}
#[test]
fn rerank_knobs_are_active_and_specific() {
let mode = knob_for_key(SEARCH_RERANK_KEY).expect("search.rerank is covered");
assert_eq!(mode.status, ConfigRuntimeStatus::Active);
assert_eq!(mode.valid_range, "auto | off");
assert!(
mode.effect.contains("disabled before model lookup"),
"rerank mode must document that off suppresses lookup"
);
let top_k = knob_for_key(SEARCH_RERANK_TOP_K_KEY).expect("rerank_top_k is covered");
assert_eq!(top_k.status, ConfigRuntimeStatus::Active);
assert_eq!(top_k.valid_range, "positive integer");
assert!(
top_k.effect.contains("Candidate pool size"),
"rerank_top_k must describe the collect-limit effect"
);
}
#[test]
fn default_speed_knob_matches_parser_tokens() {
let speed =
knob_for_key(SEARCH_DEFAULT_SPEED_KEY).expect("search.default_speed is covered");
assert_eq!(speed.status, ConfigRuntimeStatus::Active);
assert_eq!(speed.valid_range, "fast | balanced | thorough");
assert!(
!speed.valid_range.contains("instant") && !speed.valid_range.contains("quality"),
"config explain must not advertise query-schema speed tokens for search.default_speed"
);
}
#[test]
fn explain_reports_effect_value_and_source_layer() {
let explanation = explain(
SEARCH_SEMANTIC_WEIGHT_KEY,
Some("0.45".to_owned()),
Some(ConfigValueSource::Project),
)
.expect("known key explains");
let value = explanation.data_json();
assert_eq!(value["key"], SEARCH_SEMANTIC_WEIGHT_KEY);
assert_eq!(value["effectiveValue"], "0.45");
assert_eq!(value["sourceLayer"], "project");
assert_eq!(value["status"], "active");
assert!(value["caveat"].is_string());
assert_eq!(
value["lintFindings"].as_array().map(Vec::len),
Some(0),
"explanations always carry a deterministic lintFindings array"
);
}
#[test]
fn explain_unknown_key_is_none() {
assert!(explain("does.not.exist", None, None).is_none());
}
#[test]
fn explain_unset_value_falls_back_to_unknown_source() {
let explanation =
explain(MESH_ENABLED_KEY, None, None).expect("known key explains even when unset");
assert_eq!(explanation.source_layer, "unknown");
assert_eq!(explanation.data_json()["effectiveValue"], Value::Null);
}
#[test]
fn explain_data_json_keeps_required_shape_when_values_are_absent() {
let explanation = explain(MESH_ENABLED_KEY, None, None).expect("known key explains");
let value = explanation.data_json();
assert_eq!(value["schema"], CONFIG_EXPLAIN_SCHEMA_V1);
assert_eq!(value["key"], MESH_ENABLED_KEY);
assert_eq!(value["category"], "mesh");
assert_eq!(value["status"], "active");
assert_eq!(value["caveat"], Value::Null);
assert_eq!(value["effectiveValue"], Value::Null);
assert_eq!(value["sourceLayer"], "unknown");
assert!(
value["effect"]
.as_str()
.is_some_and(|text| !text.is_empty()),
"effect must be populated for human-facing explain output"
);
assert!(
value["validRange"]
.as_str()
.is_some_and(|text| !text.is_empty()),
"valid range must be populated for human-facing explain output"
);
assert_eq!(
value["lintFindings"].as_array().map(Vec::len),
Some(0),
"lintFindings is always present, even when empty"
);
}
#[test]
fn explain_source_layer_tokens_match_merge_sources() {
for (source, expected) in [
(ConfigValueSource::Cli, "cli"),
(ConfigValueSource::Environment, "environment"),
(ConfigValueSource::Project, "project"),
(ConfigValueSource::User, "user"),
(ConfigValueSource::Default, "default"),
] {
let explanation = explain(MESH_ENABLED_KEY, Some("true".to_owned()), Some(source))
.expect("known key explains");
assert_eq!(explanation.source_layer, expected);
assert_eq!(explanation.data_json()["sourceLayer"], expected);
}
}
#[test]
fn explain_can_attach_key_scoped_lint_findings() {
let facts = ConfigLintFacts {
semantic_weight: Some(0.45),
neural_tier_active: false,
search_mode: Some("lexical".to_owned()),
embedding_model_path: Some((
"embedding.model_path".to_owned(),
"/no/such".to_owned(),
false,
)),
env_vars: vec![("EMBEDDING_MODEL".to_owned(), false)],
};
let findings = run_config_lint(&facts);
let explanation = explain(
SEARCH_SEMANTIC_WEIGHT_KEY,
Some("0.45".to_owned()),
Some(ConfigValueSource::Project),
)
.expect("known key explains")
.with_lint_findings(&findings);
let json = explanation.data_json();
let attached = json["lintFindings"]
.as_array()
.expect("lint findings array");
assert_eq!(attached.len(), 2);
assert!(
attached
.iter()
.all(|finding| finding["key"] == SEARCH_SEMANTIC_WEIGHT_KEY),
"only findings for the explained key are attached"
);
assert!(
attached
.iter()
.all(|finding| finding["severity"] == CONFIG_LINT_SEVERITY),
"config-explain lint findings remain advisory-only"
);
}
#[test]
fn explain_ignores_empty_and_non_matching_lint_findings() {
let unrelated = ConfigLintFinding::advisory(
LINT_UNKNOWN_ENV_VAR,
"EMBEDDING_MODEL",
"foreign env var has no effect",
);
let empty = explain(
MESH_ENABLED_KEY,
Some("false".to_owned()),
Some(ConfigValueSource::User),
)
.expect("known key explains")
.with_lint_findings(&[]);
assert!(empty.lint_findings.is_empty());
assert_eq!(
empty.data_json()["lintFindings"].as_array().map(Vec::len),
Some(0)
);
let unrelated = explain(
MESH_ENABLED_KEY,
Some("false".to_owned()),
Some(ConfigValueSource::User),
)
.expect("known key explains")
.with_lint_findings(&[unrelated]);
assert!(
unrelated.lint_findings.is_empty(),
"findings for other keys/env vars must not leak into this key"
);
assert_eq!(
unrelated.data_json()["lintFindings"]
.as_array()
.map(Vec::len),
Some(0)
);
}
#[test]
fn lint_flags_semantic_weight_without_neural_tier() {
let finding = lint_semantic_weight_without_neural_tier(Some(0.45), false)
.expect("a weight without a neural tier is suspicious");
assert_eq!(finding.code, LINT_WEIGHT_WITHOUT_NEURAL_TIER);
assert_eq!(finding.severity, CONFIG_LINT_SEVERITY);
assert!(lint_semantic_weight_without_neural_tier(Some(0.45), true).is_none());
assert!(lint_semantic_weight_without_neural_tier(None, false).is_none());
}
#[test]
fn lint_flags_semantic_weight_boundaries_without_neural_tier() {
for weight in [0.0, 1.0] {
let finding = lint_semantic_weight_without_neural_tier(Some(weight), false)
.expect("boundary weights still need the neural-tier honesty warning");
assert_eq!(finding.code, LINT_WEIGHT_WITHOUT_NEURAL_TIER);
assert_eq!(finding.key.as_str(), SEARCH_SEMANTIC_WEIGHT_KEY);
assert!(
finding
.message
.contains(&format!("search.semantic_weight={weight}")),
"finding should report the exact configured boundary weight"
);
}
}
#[test]
fn lint_flags_missing_embedding_model_path() {
let finding = lint_embedding_model_path_missing(
"embedding.model_path",
Some("/no/such/model"),
false,
)
.expect("a missing model path is suspicious");
assert_eq!(finding.code, LINT_EMBEDDING_MODEL_PATH_MISSING);
assert!(
lint_embedding_model_path_missing("embedding.model_path", Some("/exists"), true)
.is_none()
);
assert!(lint_embedding_model_path_missing("embedding.model_path", None, false).is_none());
}
#[test]
fn lint_flags_unknown_env_var() {
let finding = lint_unknown_env_var("EMBEDDING_MODEL", false).expect("unknown var flagged");
assert_eq!(finding.code, LINT_UNKNOWN_ENV_VAR);
assert!(
finding.message.contains("ee capabilities --json")
&& finding.message.contains("data.envOverrides[]"),
"unknown-env lint must point at the shipped env-var discovery surface"
);
assert!(
!finding.message.contains("ee config env"),
"unknown-env lint must not point at the nonexistent `ee config env` command"
);
assert!(lint_unknown_env_var("EE_DB", true).is_none());
}
#[test]
fn lint_flags_contradictory_lexical_mode() {
let finding = lint_contradictory_lexical_mode(Some("lexical"), Some(0.45))
.expect("lexical mode + semantic weight is contradictory");
assert_eq!(finding.code, LINT_CONTRADICTORY_LEXICAL_MODE);
assert!(lint_contradictory_lexical_mode(Some("hybrid"), Some(0.45)).is_none());
assert!(lint_contradictory_lexical_mode(Some("lexical"), None).is_none());
}
#[test]
fn run_config_lint_is_advisory_only_and_deterministic() {
let facts = ConfigLintFacts {
semantic_weight: Some(0.45),
neural_tier_active: false,
search_mode: Some("lexical".to_owned()),
embedding_model_path: Some((
"embedding.model_path".to_owned(),
"/no/such".to_owned(),
false,
)),
env_vars: vec![
("EMBEDDING_MODEL".to_owned(), false),
("EE_DB".to_owned(), true),
],
};
let findings = run_config_lint(&facts);
assert_eq!(findings.len(), 4);
assert!(findings.iter().all(|f| f.severity == CONFIG_LINT_SEVERITY));
let again = run_config_lint(&facts);
assert_eq!(findings, again);
}
#[test]
fn run_config_lint_orders_same_code_findings_by_key() {
let facts = ConfigLintFacts {
semantic_weight: None,
neural_tier_active: false,
search_mode: None,
embedding_model_path: None,
env_vars: vec![
("EEZ_UNUSED".to_owned(), false),
("EEA_UNUSED".to_owned(), false),
("EE_REAL".to_owned(), true),
],
};
let findings = run_config_lint(&facts);
assert_eq!(findings.len(), 2);
assert_eq!(findings[0].code, LINT_UNKNOWN_ENV_VAR);
assert_eq!(findings[0].key.as_str(), "EEA_UNUSED");
assert_eq!(findings[1].code, LINT_UNKNOWN_ENV_VAR);
assert_eq!(findings[1].key.as_str(), "EEZ_UNUSED");
}
#[test]
fn run_config_lint_clean_config_has_no_findings() {
let facts = ConfigLintFacts {
semantic_weight: Some(0.45),
neural_tier_active: true,
search_mode: Some("hybrid".to_owned()),
embedding_model_path: None,
env_vars: vec![("EE_DB".to_owned(), true)],
};
assert!(run_config_lint(&facts).is_empty());
}
}