use std::path::Path;
use rusqlite::{params, Connection, OptionalExtension};
use super::types::{Check, Status};
pub(super) fn check_compiled_rules(conn: Option<&Connection>) -> Check {
let config = match crate::runtime_config::rule_compilation_config() {
Ok(config) => config,
Err(error) => {
return Check::new(
"Compiled rules",
Status::Fail,
format!("rule compilation config is invalid: {error}"),
)
}
};
let data_dir = match crate::db::absolute_data_dir() {
Ok(data_dir) => data_dir,
Err(error) => {
return Check::new(
"Compiled rules",
Status::Fail,
format!("rule data directory is unavailable: {error}"),
)
}
};
let cwd = match std::env::current_dir() {
Ok(cwd) => cwd,
Err(error) => {
return Check::new(
"Compiled rules",
Status::Fail,
format!("current project directory is unavailable: {error}"),
)
}
};
let project = crate::db::project_from_cwd(&cwd.to_string_lossy());
check_compiled_rules_for(conn, &data_dir, &project, config.enabled)
}
pub(super) fn check_rule_enforcement_capabilities() -> Vec<Check> {
vec![
Check::new(
"Rule enforcement (claude-code)",
Status::Ok,
"pre_execution=true warn=supported block=supported",
),
Check::new(
"Rule enforcement (codex-cli)",
Status::Ok,
"pre_execution=false warn=unsupported block=unsupported reason=no_pre_execution_bash_hook",
),
]
}
fn check_compiled_rules_for(
conn: Option<&Connection>,
data_dir: &Path,
project: &str,
enabled: bool,
) -> Check {
let artifact_path = crate::rules::artifact_path_for_project(data_dir, project);
let (artifact_present, artifact_valid, rule_count, last_compile_epoch, artifact_error) =
match crate::rules::load_artifact_fail_open(&artifact_path) {
crate::rules::ArtifactLoad::Loaded(artifact) => (
true,
true,
Some(artifact.rules.len()),
Some(artifact.compiled_at_epoch),
None,
),
crate::rules::ArtifactLoad::FailOpen { kind, .. } => (
kind != crate::rules::ArtifactLoadErrorKind::Missing,
false,
None,
None,
Some(artifact_error_label(kind)),
),
};
let compile = conn.map(|conn| load_compile_diagnostics_for(conn, project));
let evaluation = crate::rules::load_evaluation_error(data_dir, project);
let mut degraded = enabled && (!artifact_present || !artifact_valid);
let compile_detail = match compile {
Some(Ok(snapshot)) => {
degraded |= enabled
&& snapshot
.latest_status
.as_deref()
.is_some_and(|status| status != "ok");
format!(
"latest_compile_status={} latest_compile_event_epoch={} last_compile_error={}",
optional_text(snapshot.latest_status.as_deref()),
optional_epoch(snapshot.latest_epoch),
optional_error(snapshot.last_error_epoch),
)
}
Some(Err(_)) => {
degraded |= enabled;
"latest_compile_status=unavailable latest_compile_event_epoch=unavailable last_compile_error=unavailable".to_string()
}
None => {
degraded |= enabled;
"latest_compile_status=unavailable latest_compile_event_epoch=unavailable last_compile_error=unavailable".to_string()
}
};
let evaluation_detail = match evaluation {
Ok(snapshot) => {
degraded |= enabled && snapshot.corrupt_markers > 0;
let latest = snapshot.latest.as_ref().map_or_else(
|| "none".to_string(),
|record| {
let codes = record
.codes
.iter()
.map(|code| code.as_str())
.collect::<Vec<_>>()
.join(",");
format!("{codes}@{}", record.occurred_at_epoch)
},
);
format!(
"last_evaluation_error={latest} evaluation_diagnostic_corrupt={}",
snapshot.corrupt_markers
)
}
Err(_) => {
degraded = true;
"last_evaluation_error=unavailable evaluation_diagnostic_corrupt=unavailable"
.to_string()
}
};
let status = if degraded { Status::Warn } else { Status::Ok };
Check::new(
"Compiled rules",
status,
format!(
"enabled={enabled} artifact_present={artifact_present} artifact_valid={artifact_valid} artifact_error={} rule_count={} last_compile_epoch={} {compile_detail} {evaluation_detail}",
artifact_error.unwrap_or("none"),
optional_count(rule_count),
optional_epoch(last_compile_epoch),
),
)
}
struct CompileDiagnosticSnapshot {
latest_status: Option<String>,
latest_epoch: Option<i64>,
last_error_epoch: Option<i64>,
}
fn load_compile_diagnostics_for(
conn: &Connection,
project: &str,
) -> rusqlite::Result<CompileDiagnosticSnapshot> {
let latest = conn
.query_row(
"SELECT status, occurred_at_epoch
FROM preference_rule_diagnostics
WHERE project = ?1 AND event_kind = 'compile'
ORDER BY id DESC LIMIT 1",
params![project],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)),
)
.optional()?;
let last_error_epoch = conn
.query_row(
"SELECT occurred_at_epoch
FROM preference_rule_diagnostics
WHERE project = ?1 AND event_kind = 'compile' AND status = 'error'
ORDER BY id DESC LIMIT 1",
params![project],
|row| row.get::<_, i64>(0),
)
.optional()?;
Ok(CompileDiagnosticSnapshot {
latest_status: latest.as_ref().map(|(status, _)| status.clone()),
latest_epoch: latest.map(|(_, epoch)| epoch),
last_error_epoch,
})
}
fn artifact_error_label(kind: crate::rules::ArtifactLoadErrorKind) -> &'static str {
match kind {
crate::rules::ArtifactLoadErrorKind::Missing => "missing",
crate::rules::ArtifactLoadErrorKind::Read => "read",
crate::rules::ArtifactLoadErrorKind::Parse => "parse",
crate::rules::ArtifactLoadErrorKind::Validate => "validate",
}
}
fn optional_text(value: Option<&str>) -> &str {
value.unwrap_or("none")
}
fn optional_epoch(value: Option<i64>) -> String {
value.map_or_else(|| "none".to_string(), |value| value.to_string())
}
fn optional_count(value: Option<usize>) -> String {
value.map_or_else(|| "none".to_string(), |value| value.to_string())
}
fn optional_error(value: Option<i64>) -> String {
value.map_or_else(|| "none".to_string(), |value| format!("present@{value}"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::test_support::ScopedTestDataDir;
use crate::rules::{
write_artifact_atomic, CompiledRule, CompiledRulesArtifact, EvaluationDiagnosticCode,
RuleAction, RuleOverrideState, RulePredicate,
};
#[test]
fn reports_artifact_and_errors_without_rule_payload_secrets() -> anyhow::Result<()> {
let scoped = ScopedTestDataDir::new("doctor-compiled-rules");
let conn = crate::db::open_db()?;
let project = "/repo/private";
let artifact = CompiledRulesArtifact::new(
1234,
vec![CompiledRule {
rule_id: "pref-1-1".to_string(),
source_memory_id: 1,
reinforcement_count: 3,
action: RuleAction::Warn,
override_state: RuleOverrideState {
disabled: false,
action_override: None,
},
predicate: RulePredicate::CommandRegex {
pattern: "TOP_SECRET_PATTERN".to_string(),
message: "TOP_SECRET_MESSAGE".to_string(),
},
}],
);
write_artifact_atomic(
crate::rules::artifact_path_for_project(&scoped.path, project),
&artifact,
)?;
conn.execute(
"INSERT INTO preference_rule_diagnostics
(project, event_kind, status, message, occurred_at_epoch)
VALUES (?1, 'compile', 'error', 'TOP_SECRET_DIAGNOSTIC', 1240)",
[project],
)?;
crate::rules::log_evaluation_error_once_with_diagnostic(
&scoped.path,
Some("doctor-session"),
Some(project),
&[EvaluationDiagnosticCode::RuleEvaluation],
"TOP_SECRET_EVALUATION",
);
let check = check_compiled_rules_for(Some(&conn), &scoped.path, project, true);
assert!(matches!(check.status, Status::Warn));
assert!(check.detail.contains("artifact_present=true"));
assert!(check.detail.contains("artifact_valid=true"));
assert!(check.detail.contains("rule_count=1"));
assert!(check.detail.contains("last_compile_epoch=1234"));
assert!(check.detail.contains("last_compile_error=present@1240"));
assert!(check
.detail
.contains("last_evaluation_error=rule_evaluation@"));
assert!(!check.detail.contains("TOP_SECRET"), "{}", check.detail);
Ok(())
}
#[test]
fn disabled_rollout_reports_missing_artifact_without_warning() {
let data_dir = crate::rules::test_support::test_dir("doctor-rules-disabled");
let check = check_compiled_rules_for(None, &data_dir, "/repo", false);
assert!(matches!(check.status, Status::Ok));
assert!(check.detail.contains("enabled=false"));
assert!(check.detail.contains("artifact_present=false"));
}
#[test]
fn historical_evaluation_error_does_not_degrade_current_healthy_artifact() -> anyhow::Result<()>
{
let scoped = ScopedTestDataDir::new("doctor-rules-recovered");
let conn = crate::db::open_db()?;
let project = "/repo/recovered";
let artifact = CompiledRulesArtifact::new(2000, Vec::new());
write_artifact_atomic(
crate::rules::artifact_path_for_project(&scoped.path, project),
&artifact,
)?;
conn.execute(
"INSERT INTO preference_rule_diagnostics
(project, event_kind, status, message, occurred_at_epoch)
VALUES (?1, 'compile', 'ok', 'compiled 0 rule(s)', 2000)",
[project],
)?;
crate::rules::log_evaluation_error_once_with_diagnostic(
&scoped.path,
Some("doctor-recovered-session"),
Some(project),
&[EvaluationDiagnosticCode::ArtifactMissing],
"historical missing artifact",
);
let check = check_compiled_rules_for(Some(&conn), &scoped.path, project, true);
assert!(matches!(check.status, Status::Ok), "{}", check.detail);
assert!(check
.detail
.contains("last_evaluation_error=artifact_missing@"));
Ok(())
}
#[test]
fn host_capabilities_are_explicit_and_honest() {
let checks = check_rule_enforcement_capabilities();
assert_eq!(checks.len(), 2);
assert!(checks[0].detail.contains("block=supported"));
assert!(checks[1].detail.contains("block=unsupported"));
assert!(checks[1].detail.contains("pre_execution=false"));
}
}