use std::collections::BTreeMap;
use harn_glob::match_name as glob_match;
use serde::Serialize;
use super::catalog::is_logical_model_selector;
use super::{ModelDef, ProvidersConfig};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct OverlayFinding {
pub section: &'static str,
pub key: String,
pub kind: OverlayFindingKind,
}
impl OverlayFinding {
pub fn address(&self) -> String {
if self.key == self.section {
return self.section.to_string();
}
let bare = !self.key.is_empty()
&& self.key.chars().all(|character| {
character.is_ascii_alphanumeric() || matches!(character, '_' | '-')
});
if bare {
format!("{}.{}", self.section, self.key)
} else {
format!("{}.{:?}", self.section, self.key)
}
}
pub fn is_actionable(&self) -> bool {
match self.kind {
OverlayFindingKind::Redundant { .. } | OverlayFindingKind::Narrowable { .. } => true,
OverlayFindingKind::Dangling { .. }
| OverlayFindingKind::DuplicateOfBaseline { .. } => false,
}
}
pub fn preserves_catalog(&self) -> bool {
self.restored_fields().is_some_and(<[String]>::is_empty)
}
pub fn restored_fields(&self) -> Option<&[String]> {
match &self.kind {
OverlayFindingKind::Redundant { restored_fields }
| OverlayFindingKind::Narrowable {
restored_fields, ..
} => Some(restored_fields),
OverlayFindingKind::Dangling { .. } => Some(&[]),
OverlayFindingKind::DuplicateOfBaseline { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "finding", rename_all = "snake_case")]
pub enum OverlayFindingKind {
Redundant {
restored_fields: Vec<String>,
},
Narrowable {
patch_toml: String,
inherited_fields: Vec<String>,
restored_fields: Vec<String>,
},
Dangling { target: String },
DuplicateOfBaseline { baseline_index: usize },
}
pub fn audit_overlay(baseline: &ProvidersConfig, overlay: &ProvidersConfig) -> Vec<OverlayFinding> {
let merged = merge(baseline, overlay);
let mut findings = Vec::new();
for section in SECTIONS {
for key in (section.keys)(overlay) {
findings.extend(audit_entry(baseline, overlay, &merged, section, key));
}
}
findings
}
#[cfg(test)]
pub(super) fn strip_all_auditable_entries(overlay: &ProvidersConfig) -> ProvidersConfig {
let mut stripped = overlay.clone();
for section in SECTIONS {
for key in (section.keys)(overlay) {
(section.remove)(&mut stripped, &key);
}
}
stripped
}
fn audit_entry(
baseline: &ProvidersConfig,
overlay: &ProvidersConfig,
merged: &ProvidersConfig,
section: &SectionSpec,
key: String,
) -> Option<OverlayFinding> {
let finding = |kind| {
Some(OverlayFinding {
section: section.name,
key: key.clone(),
kind,
})
};
if let Some(target) = (section.dangling_target)(merged, &key) {
return finding(OverlayFindingKind::Dangling { target });
}
if merge(baseline, &without(overlay, section, &key)) == *merged {
return finding(OverlayFindingKind::Redundant {
restored_fields: Vec::new(),
});
}
if let Some(index) = (section.baseline_duplicate)(baseline, overlay, &key) {
return finding(OverlayFindingKind::DuplicateOfBaseline {
baseline_index: index,
});
}
if section.name == MODELS_SECTION {
if let Some(kind) = audit_model_row(baseline, overlay, merged, &key) {
return finding(kind);
}
}
None
}
fn audit_model_row(
baseline: &ProvidersConfig,
overlay: &ProvidersConfig,
merged: &ProvidersConfig,
id: &str,
) -> Option<OverlayFindingKind> {
let baseline_row = row_table(baseline.models.get(id)?)?;
let overlay_row = row_table(overlay.models.get(id)?)?;
let delta = row_delta(&baseline_row, &overlay_row);
if !delta.patch.is_empty() && delta.patch.len() >= overlay_row.len() {
return None;
}
let mut rewritten = overlay.clone();
rewritten.models.remove(id);
if !delta.patch.is_empty() {
rewritten
.patch
.models
.insert(id.to_string(), toml::Value::Table(delta.patch.clone()));
}
if !catalogs_agree_except(&merge(baseline, &rewritten), merged, id, &delta.restored) {
return None;
}
if delta.patch.is_empty() {
return Some(OverlayFindingKind::Redundant {
restored_fields: delta.restored,
});
}
let inherited_fields = overlay_row
.keys()
.filter(|field| !delta.patch.contains_key(*field))
.cloned()
.collect();
Some(OverlayFindingKind::Narrowable {
patch_toml: render_patch(id, &delta.patch),
inherited_fields,
restored_fields: delta.restored,
})
}
struct RowDelta {
patch: toml::Table,
restored: Vec<String>,
}
fn row_delta(baseline_row: &toml::Table, overlay_row: &toml::Table) -> RowDelta {
let mut delta = RowDelta {
patch: toml::Table::new(),
restored: Vec::new(),
};
collect_row_delta("", baseline_row, overlay_row, &mut delta);
delta
}
fn collect_row_delta(
prefix: &str,
baseline_row: &toml::Table,
overlay_row: &toml::Table,
delta: &mut RowDelta,
) {
for key in baseline_row.keys() {
if !overlay_row.contains_key(key) {
delta.restored.push(format!("{prefix}{key}"));
}
}
for (key, overlay_value) in overlay_row {
match (baseline_row.get(key), overlay_value) {
(Some(baseline_value), _) if baseline_value == overlay_value => {}
(Some(toml::Value::Table(baseline_table)), toml::Value::Table(overlay_table)) => {
let mut nested = RowDelta {
patch: toml::Table::new(),
restored: Vec::new(),
};
collect_row_delta(
&format!("{prefix}{key}."),
baseline_table,
overlay_table,
&mut nested,
);
if !nested.patch.is_empty() {
delta
.patch
.insert(key.clone(), toml::Value::Table(nested.patch));
}
delta.restored.extend(nested.restored);
}
_ => {
delta.patch.insert(key.clone(), overlay_value.clone());
}
}
}
}
fn catalogs_agree_except(
rewritten: &ProvidersConfig,
original: &ProvidersConfig,
id: &str,
restored: &[String],
) -> bool {
if restored.is_empty() {
return rewritten == original;
}
let (Some(rewritten_row), Some(original_row)) =
(rewritten.models.get(id), original.models.get(id))
else {
return false;
};
let mut rewritten_rest = rewritten.clone();
let mut original_rest = original.clone();
rewritten_rest.models.remove(id);
original_rest.models.remove(id);
if rewritten_rest != original_rest {
return false;
}
let (Some(mut left), Some(mut right)) = (row_table(rewritten_row), row_table(original_row))
else {
return false;
};
for path in restored {
remove_path(&mut left, path);
remove_path(&mut right, path);
}
left == right
}
fn remove_path(table: &mut toml::Table, path: &str) {
match path.split_once('.') {
None => {
table.remove(path);
}
Some((head, rest)) => {
if let Some(toml::Value::Table(child)) = table.get_mut(head) {
remove_path(child, rest);
}
}
}
}
fn row_table(row: &ModelDef) -> Option<toml::Table> {
match toml::Value::try_from(row).ok()? {
toml::Value::Table(table) => Some(table),
_ => None,
}
}
fn render_patch(id: &str, patch: &toml::Table) -> String {
let mut models = toml::Table::new();
models.insert(id.to_string(), toml::Value::Table(patch.clone()));
let mut patch_section = toml::Table::new();
patch_section.insert("models".to_string(), toml::Value::Table(models));
let mut document = toml::Table::new();
document.insert("patch".to_string(), toml::Value::Table(patch_section));
toml::to_string_pretty(&document)
.unwrap_or_else(|error| format!("# failed to render patch: {error}\n"))
}
fn merge(baseline: &ProvidersConfig, overlay: &ProvidersConfig) -> ProvidersConfig {
let mut merged = baseline.clone();
merged.merge_from(overlay);
merged.patch.models.clear();
merged
}
fn without(overlay: &ProvidersConfig, section: &SectionSpec, key: &str) -> ProvidersConfig {
let mut trimmed = overlay.clone();
(section.remove)(&mut trimmed, key);
trimmed
}
const MODELS_SECTION: &str = "models";
struct SectionSpec {
name: &'static str,
keys: fn(&ProvidersConfig) -> Vec<String>,
remove: fn(&mut ProvidersConfig, &str),
dangling_target: fn(&ProvidersConfig, &str) -> Option<String>,
baseline_duplicate: fn(&ProvidersConfig, &ProvidersConfig, &str) -> Option<usize>,
}
fn no_dangling_target(_merged: &ProvidersConfig, _key: &str) -> Option<String> {
None
}
fn no_baseline_duplicate(
_baseline: &ProvidersConfig,
_overlay: &ProvidersConfig,
_key: &str,
) -> Option<usize> {
None
}
const SECTIONS: &[SectionSpec] = &[
SectionSpec {
name: "default_provider",
keys: |overlay| singleton("default_provider", overlay.default_provider.is_some()),
remove: |overlay, _| overlay.default_provider = None,
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "providers",
keys: |overlay| map_keys(&overlay.providers),
remove: |overlay, key| {
overlay.providers.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "aliases",
keys: |overlay| map_keys(&overlay.aliases),
remove: |overlay, key| {
overlay.aliases.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "alias_tool_calling",
keys: |overlay| map_keys(&overlay.alias_tool_calling),
remove: |overlay, key| {
overlay.alias_tool_calling.remove(key);
},
dangling_target: |merged, key| {
(!merged.aliases.contains_key(key)).then(|| format!("alias {key}"))
},
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: MODELS_SECTION,
keys: |overlay| map_keys(&overlay.models),
remove: |overlay, key| {
overlay.models.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "patch.models",
keys: |overlay| map_keys(&overlay.patch.models),
remove: |overlay, key| {
overlay.patch.models.remove(key);
},
dangling_target: |merged, key| {
(!merged.models.contains_key(key)).then(|| format!("model {key}"))
},
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "qc_defaults",
keys: |overlay| map_keys(&overlay.qc_defaults),
remove: |overlay, key| {
overlay.qc_defaults.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "model_defaults",
keys: |overlay| map_keys(&overlay.model_defaults),
remove: |overlay, key| {
overlay.model_defaults.remove(key);
},
dangling_target: |merged, key| {
(!matches_any_route(merged, key)).then(|| format!("route pattern {key}"))
},
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "model_roles",
keys: |overlay| map_keys(&overlay.model_roles),
remove: |overlay, key| {
overlay.model_roles.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "model_ladders",
keys: |overlay| map_keys(&overlay.model_ladders),
remove: |overlay, key| {
overlay.model_ladders.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "presentation.variants",
keys: |overlay| map_keys(&overlay.presentation.variants),
remove: |overlay, key| {
overlay.presentation.variants.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "presentation.families",
keys: |overlay| map_keys(&overlay.presentation.families),
remove: |overlay, key| {
overlay.presentation.families.remove(key);
},
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "suppress.routes",
keys: |overlay| overlay.suppress.routes.clone(),
remove: |overlay, key| overlay.suppress.routes.retain(|route| route != key),
dangling_target: |merged, key| {
(!suppresses_a_route(merged, key)).then(|| format!("route {key}"))
},
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "tier_defaults",
keys: |overlay| {
singleton(
"tier_defaults",
overlay.tier_defaults != super::TierDefaults::default(),
)
},
remove: |overlay, _| overlay.tier_defaults = super::TierDefaults::default(),
dangling_target: no_dangling_target,
baseline_duplicate: no_baseline_duplicate,
},
SectionSpec {
name: "inference_rules",
keys: |overlay| indices(overlay.inference_rules.len()),
remove: |overlay, key| remove_at(&mut overlay.inference_rules, key),
dangling_target: no_dangling_target,
baseline_duplicate: |baseline, overlay, key| {
index_of_equal(&baseline.inference_rules, &overlay.inference_rules, key)
},
},
SectionSpec {
name: "tier_rules",
keys: |overlay| indices(overlay.tier_rules.len()),
remove: |overlay, key| remove_at(&mut overlay.tier_rules, key),
dangling_target: no_dangling_target,
baseline_duplicate: |baseline, overlay, key| {
index_of_equal(&baseline.tier_rules, &overlay.tier_rules, key)
},
},
];
fn map_keys<V>(map: &BTreeMap<String, V>) -> Vec<String> {
map.keys().cloned().collect()
}
fn singleton(name: &'static str, present: bool) -> Vec<String> {
if present {
vec![name.to_string()]
} else {
Vec::new()
}
}
fn indices(len: usize) -> Vec<String> {
(0..len).map(|index| index.to_string()).collect()
}
fn remove_at<T>(items: &mut Vec<T>, key: &str) {
if let Ok(index) = key.parse::<usize>() {
if index < items.len() {
items.remove(index);
}
}
}
fn index_of_equal<T: PartialEq>(baseline: &[T], overlay: &[T], key: &str) -> Option<usize> {
let entry = overlay.get(key.parse::<usize>().ok()?)?;
baseline.iter().position(|candidate| candidate == entry)
}
fn matches_any_route(merged: &ProvidersConfig, pattern: &str) -> bool {
if is_logical_model_selector(pattern) {
return true;
}
merged.models.iter().any(|(id, model)| {
route_identities(id, model).any(|identity| glob_match(pattern, &identity))
})
}
fn route_identities<'a>(id: &'a str, model: &'a ModelDef) -> impl Iterator<Item = String> + 'a {
[
Some(id.to_string()),
Some(format!("{}/{id}", model.provider)),
model.wire_model.clone(),
]
.into_iter()
.flatten()
}
fn suppresses_a_route(merged: &ProvidersConfig, route: &str) -> bool {
let Some((provider, model_id)) = route.split_once(':') else {
return false;
};
let hides_model = merged
.models
.get(model_id)
.is_some_and(|model| model.provider == provider);
hides_model
|| merged
.aliases
.values()
.any(|alias| alias.provider == provider && alias.id == model_id)
}