use std::path::PathBuf;
use crate::analyzer::env_truth::{
self, ComputeConfig, DEFAULT_STALE_THRESHOLD_DAYS, EnvTruthReport, FailOnKind, RenderOptions,
};
use crate::cli::command::{EnvTruthOptions, GlobalOptions};
use crate::snapshot::{Snapshot, resolve_snapshot_root};
use super::super::DispatchResult;
pub fn run(opts: &EnvTruthOptions, global: &GlobalOptions) -> DispatchResult {
let roots: Vec<PathBuf> = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
let snapshot_root = resolve_snapshot_root(&roots);
let snapshot = match Snapshot::load(&snapshot_root) {
Ok(s) => Some(s),
Err(err) => {
if !global.quiet {
eprintln!(
"[loct][env-truth] snapshot not loaded ({err}); proceeding with declaration-only audit"
);
}
None
}
};
let cfg_override = env_truth::load_config_for(&snapshot_root);
let stale_threshold = opts
.stale_threshold_days
.or_else(|| cfg_override.as_ref().and_then(|c| c.stale_threshold_days))
.unwrap_or(DEFAULT_STALE_THRESHOLD_DAYS);
let cfg = ComputeConfig {
roots: roots.clone(),
restricted_paths: opts.restricted_paths.clone(),
precedence_override: cfg_override,
stale_threshold_days: stale_threshold,
quiet: global.quiet,
};
let mut report = env_truth::compute_env_truth(&cfg, snapshot.as_ref());
if let Some(name) = &opts.name {
report.declarations.retain(|d| d.name == *name);
report.orphan_reads.retain(|o| o.name == *name);
}
if opts.no_orphans {
report.orphan_reads.clear();
}
let fail_kinds: Vec<FailOnKind> = match parse_fail_on(&opts.fail_on) {
Ok(v) => v,
Err(msg) => {
eprintln!("[loct][env-truth] {msg}");
return DispatchResult::Exit(2);
}
};
if opts.json || global.json {
match serde_json::to_string_pretty(&report) {
Ok(json) => println!("{json}"),
Err(err) => {
eprintln!("[loct][env-truth] failed to serialize report: {err}");
return DispatchResult::Exit(1);
}
}
} else {
let render_opts = RenderOptions {
all: opts.all,
show_hashes: opts.show_hashes,
};
let md = env_truth::render_markdown(&report, &render_opts);
print!("{md}");
}
if !fail_kinds.is_empty() {
let triggered = first_trigger(&report, &fail_kinds);
if let Some((env_name, kind)) = triggered {
if !global.quiet {
eprintln!(
"[loct][env-truth] --fail-on {kind:?} matched on '{env_name}' — exiting 2"
);
}
return DispatchResult::Exit(2);
}
}
DispatchResult::Exit(0)
}
fn parse_fail_on(tokens: &[String]) -> Result<Vec<FailOnKind>, String> {
let mut out = Vec::with_capacity(tokens.len());
for tok in tokens {
match FailOnKind::from_cli(tok) {
Some(k) => out.push(k),
None => {
return Err(format!(
"unknown --fail-on kind '{tok}'. Allowed: stale-sealed-overrides-fresh-plain, stale-overrides-fresh, multi-source-mismatch, orphan-code-reference, orphan-declaration, any"
));
}
}
}
Ok(out)
}
fn first_trigger<'a>(
report: &'a EnvTruthReport,
kinds: &[FailOnKind],
) -> Option<(&'a str, FailOnKind)> {
for decl in &report.declarations {
for warning in &decl.precedence_warnings {
for kind in kinds {
if kind.matches(warning) {
return Some((decl.name.as_str(), *kind));
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analyzer::env_truth::EnvWarning;
fn warning_kind_str(w: &EnvWarning) -> &'static str {
match w {
EnvWarning::StaleOverridesFresh { .. } => "stale_overrides_fresh",
EnvWarning::MultiSourceValueMismatch { .. } => "multi_source_value_mismatch",
EnvWarning::OrphanCodeReference { .. } => "orphan_code_reference",
EnvWarning::OrphanDeclaration { .. } => "orphan_declaration",
EnvWarning::SealedSecretSuspectedStale { .. } => "sealed_secret_suspected_stale",
EnvWarning::EncryptedDecodeBlocked { .. } => "encrypted_decode_blocked",
}
}
#[test]
fn parse_fail_on_rejects_unknown_token() {
let r = parse_fail_on(&["bogus-kind".into()]);
assert!(r.is_err());
}
#[test]
fn parse_fail_on_accepts_known_tokens() {
let r =
parse_fail_on(&["stale-sealed-overrides-fresh-plain".into(), "any".into()]).unwrap();
assert_eq!(r.len(), 2);
assert!(matches!(r[0], FailOnKind::StaleSealedOverridesFreshPlain));
assert!(matches!(r[1], FailOnKind::AnyWarning));
let w = EnvWarning::SealedSecretSuspectedStale {
sealed_path: "p".into(),
sealed_age_days: 30,
plain_age_days: 1,
};
assert_eq!(warning_kind_str(&w), "sealed_secret_suspected_stale");
}
}