use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use crate::db::{
DbConnection, StoredFeedbackEvent, StoredFeedbackQuarantine, StoredTrustQuarantine,
};
use crate::policy::{DecayConfig, SourceTrustState, TrustAdvisory, TrustDecayCalculator};
const MIGRATE_REPAIR_COMMAND: &str = "ee migrate run --workspace . --json";
#[derive(Clone, Debug)]
pub struct QuarantineReport {
pub version: &'static str,
pub quarantined_sources: Vec<QuarantineEntry>,
pub at_risk_sources: Vec<QuarantineEntry>,
pub blocked_sources: Vec<QuarantineEntry>,
pub summary: QuarantineSummary,
pub storage_status: QuarantineStorageStatus,
pub workspace_path: Option<String>,
pub database_path: Option<String>,
pub degraded: Vec<QuarantineDegradation>,
}
#[derive(Clone, Debug)]
pub struct QuarantineEntry {
pub source_id: String,
pub advisory: AdvisoryLevel,
pub effective_trust: f32,
pub decay_factor: f32,
pub negative_rate: f32,
pub negative_count: u32,
pub total_imports: u32,
pub message: String,
pub permits_import: bool,
pub requires_validation: bool,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct QuarantineSummary {
pub quarantined_count: u32,
pub at_risk_count: u32,
pub blocked_count: u32,
pub total_sources: u32,
pub healthy_count: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuarantineStorageStatus {
Ready,
Missing,
Unavailable,
}
impl QuarantineStorageStatus {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Ready => "ready",
Self::Missing => "missing",
Self::Unavailable => "unavailable",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct QuarantineDegradation {
pub code: &'static str,
pub severity: &'static str,
pub message: String,
pub repair: &'static str,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AdvisoryLevel {
Allow,
Warn,
Quarantine,
Block,
}
impl AdvisoryLevel {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Allow => "allow",
Self::Warn => "warn",
Self::Quarantine => "quarantine",
Self::Block => "block",
}
}
#[must_use]
pub const fn is_problematic(self) -> bool {
matches!(self, Self::Warn | Self::Quarantine | Self::Block)
}
}
impl From<&TrustAdvisory> for AdvisoryLevel {
fn from(advisory: &TrustAdvisory) -> Self {
match advisory {
TrustAdvisory::Allow { .. } => Self::Allow,
TrustAdvisory::Warn { .. } => Self::Warn,
TrustAdvisory::Quarantine { .. } => Self::Quarantine,
TrustAdvisory::Block { .. } => Self::Block,
}
}
}
impl QuarantineReport {
#[must_use]
pub fn gather() -> Self {
Self::gather_with_sources(&[])
}
#[must_use]
pub fn gather_for_workspace(workspace_path: &Path) -> Self {
let workspace_path = match canonical_workspace_path(workspace_path) {
Ok(path) => path,
Err(message) => {
return Self::gather_with_storage(
Vec::new(),
QuarantineStorageStatus::Unavailable,
None,
None,
vec![QuarantineDegradation {
code: "quarantine_workspace_unavailable",
severity: "medium",
message: format!("Workspace unavailable: {message}"),
repair: "ee init --workspace .",
}],
);
}
};
let database_path = workspace_path.join(".ee").join("ee.db");
match quarantine_database_path_state(&database_path) {
Ok(QuarantineDatabasePathState::Ready) => {}
Ok(QuarantineDatabasePathState::Missing) => {
return Self::gather_with_storage(
Vec::new(),
QuarantineStorageStatus::Missing,
Some(workspace_path.display().to_string()),
Some(database_path.display().to_string()),
vec![QuarantineDegradation {
code: "quarantine_database_missing",
severity: "medium",
message: format!(
"No ee database was found at {}.",
database_path.display()
),
repair: "ee init --workspace .",
}],
);
}
Err(error) => {
return Self::gather_with_storage(
Vec::new(),
QuarantineStorageStatus::Unavailable,
Some(workspace_path.display().to_string()),
Some(database_path.display().to_string()),
vec![QuarantineDegradation {
code: "quarantine_database_unreadable",
severity: "medium",
message: format!("Failed to inspect quarantine database: {error}."),
repair: "ee doctor --json",
}],
);
}
}
let connection = match DbConnection::open_file(&database_path) {
Ok(connection) => connection,
Err(error) => {
return Self::gather_with_storage(
Vec::new(),
QuarantineStorageStatus::Unavailable,
Some(workspace_path.display().to_string()),
Some(database_path.display().to_string()),
vec![QuarantineDegradation {
code: "quarantine_database_unreadable",
severity: "medium",
message: format!("Failed to open quarantine database: {error}."),
repair: "ee doctor --json",
}],
);
}
};
let workspace_id = match crate::core::workspace::bound_workspace_id_or_hash(
&connection,
&super::curate::stable_workspace_id(&workspace_path),
&[&workspace_path],
) {
Ok(workspace_id) => workspace_id,
Err(error) => {
return Self::gather_with_storage(
Vec::new(),
QuarantineStorageStatus::Unavailable,
Some(workspace_path.display().to_string()),
Some(database_path.display().to_string()),
vec![QuarantineDegradation {
code: "quarantine_database_unreadable",
severity: "medium",
message: format!("Failed to resolve workspace for quarantine: {error}."),
repair: "ee doctor --json",
}],
);
}
};
let mut states = BTreeMap::<String, SourceTrustState>::new();
let mut degraded = Vec::new();
match connection.list_feedback_events(&workspace_id) {
Ok(events) => {
for event in &events {
apply_feedback_event(&mut states, event);
}
}
Err(error) => degraded.push(QuarantineDegradation {
code: "quarantine_feedback_events_unreadable",
severity: "medium",
message: format!("Failed to read feedback events: {error}."),
repair: MIGRATE_REPAIR_COMMAND,
}),
}
match connection.list_feedback_quarantine(&workspace_id, None) {
Ok(rows) => {
for row in &rows {
apply_quarantine_row(&mut states, row);
}
}
Err(error) => degraded.push(QuarantineDegradation {
code: "quarantine_rows_unreadable",
severity: "medium",
message: format!("Failed to read feedback quarantine rows: {error}."),
repair: MIGRATE_REPAIR_COMMAND,
}),
}
match connection.list_trust_quarantine(&workspace_id, true) {
Ok(rows) => {
for row in &rows {
apply_trust_quarantine_row(&mut states, row);
}
}
Err(error) => degraded.push(QuarantineDegradation {
code: "trust_quarantine_rows_unreadable",
severity: "medium",
message: format!("Failed to read source trust quarantine rows: {error}."),
repair: MIGRATE_REPAIR_COMMAND,
}),
}
let storage_status = if degraded.is_empty() {
QuarantineStorageStatus::Ready
} else {
QuarantineStorageStatus::Unavailable
};
Self::gather_with_storage(
states.into_values().collect(),
storage_status,
Some(workspace_path.display().to_string()),
Some(database_path.display().to_string()),
degraded,
)
}
#[must_use]
pub fn gather_with_sources(sources: &[SourceTrustState]) -> Self {
Self::gather_with_storage(
sources.to_vec(),
QuarantineStorageStatus::Ready,
None,
None,
Vec::new(),
)
}
fn gather_with_storage(
mut sources: Vec<SourceTrustState>,
storage_status: QuarantineStorageStatus,
workspace_path: Option<String>,
database_path: Option<String>,
degraded: Vec<QuarantineDegradation>,
) -> Self {
sources.sort_by(|left, right| left.source_id.cmp(&right.source_id));
let calculator = TrustDecayCalculator::new();
let _config = DecayConfig::default();
let mut quarantined_sources = Vec::new();
let mut at_risk_sources = Vec::new();
let mut blocked_sources = Vec::new();
let mut quarantined_count = 0u32;
let mut at_risk_count = 0u32;
let mut blocked_count = 0u32;
let mut healthy_count = 0u32;
for state in &sources {
let advisory = calculator.advisory(state);
let level = AdvisoryLevel::from(&advisory);
let effective_trust = calculator.effective_trust(state);
let decay_factor = calculator.calculate_decay_factor(state);
let entry = QuarantineEntry {
source_id: state.source_id.clone(),
advisory: level,
effective_trust,
decay_factor,
negative_rate: state.negative_rate(),
negative_count: state.negative_signal_count(),
total_imports: state.total_imports,
message: advisory_message(&advisory),
permits_import: advisory.permits_import(),
requires_validation: advisory.requires_validation(),
};
match level {
AdvisoryLevel::Allow => {
healthy_count = healthy_count.saturating_add(1);
}
AdvisoryLevel::Warn => {
at_risk_count = at_risk_count.saturating_add(1);
at_risk_sources.push(entry);
}
AdvisoryLevel::Quarantine => {
quarantined_count = quarantined_count.saturating_add(1);
quarantined_sources.push(entry);
}
AdvisoryLevel::Block => {
blocked_count = blocked_count.saturating_add(1);
blocked_sources.push(entry);
}
}
}
#[allow(clippy::cast_possible_truncation)]
let total_sources = sources.len() as u32;
Self {
version: env!("CARGO_PKG_VERSION"),
quarantined_sources,
at_risk_sources,
blocked_sources,
summary: QuarantineSummary {
quarantined_count,
at_risk_count,
blocked_count,
total_sources,
healthy_count,
},
storage_status,
workspace_path,
database_path,
degraded,
}
}
#[must_use]
pub fn has_issues(&self) -> bool {
self.summary.quarantined_count > 0
|| self.summary.at_risk_count > 0
|| self.summary.blocked_count > 0
}
#[must_use]
pub fn issue_count(&self) -> u32 {
self.summary
.quarantined_count
.saturating_add(self.summary.at_risk_count)
.saturating_add(self.summary.blocked_count)
}
pub fn filter_active_only(&mut self) {
self.at_risk_sources.clear();
self.summary.at_risk_count = 0;
self.summary.healthy_count = 0;
#[allow(clippy::cast_possible_truncation)]
{
self.summary.total_sources =
(self.quarantined_sources.len() + self.blocked_sources.len()) as u32;
}
}
}
fn canonical_workspace_path(path: &Path) -> Result<PathBuf, String> {
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(path)
};
absolute.canonicalize().map_err(|error| {
format!(
"Failed to resolve workspace {}: {error}",
absolute.display()
)
})
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum QuarantineDatabasePathState {
Ready,
Missing,
}
fn quarantine_database_path_state(path: &Path) -> io::Result<QuarantineDatabasePathState> {
if let Some(symlink_path) = first_existing_quarantine_symlink_component(path)? {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"quarantine database path {} traverses symlinked component {}",
path.display(),
symlink_path.display()
),
));
}
match fs::symlink_metadata(path) {
Ok(metadata) if metadata.file_type().is_file() => Ok(QuarantineDatabasePathState::Ready),
Ok(_) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"quarantine database path is not a regular file: {}",
path.display()
),
)),
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
Ok(QuarantineDatabasePathState::Missing)
}
Err(error) => Err(error),
}
}
fn first_existing_quarantine_symlink_component(path: &Path) -> io::Result<Option<PathBuf>> {
let mut current = PathBuf::new();
for component in path.components() {
current.push(component.as_os_str());
match fs::symlink_metadata(¤t) {
Ok(metadata) if metadata.file_type().is_symlink() => return Ok(Some(current)),
Ok(_) => {}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(None);
}
Err(error) => return Err(error),
}
}
Ok(None)
}
fn apply_feedback_event(
states: &mut BTreeMap<String, SourceTrustState>,
event: &StoredFeedbackEvent,
) {
let Some(source_id) = normalized_source_id(event.source_id.as_deref()) else {
return;
};
let state = states
.entry(source_id.clone())
.or_insert_with(|| SourceTrustState::new(source_id));
state.record_import();
match event.signal.as_str() {
"positive" | "helpful" | "confirmation" => state.record_positive(),
"contradiction" => state.record_contradiction(),
"harmful" | "negative" => state.record_harmful(),
"inaccurate" | "stale" | "outdated" => state.record_inaccurate(),
_ => {}
}
}
fn apply_quarantine_row(
states: &mut BTreeMap<String, SourceTrustState>,
row: &StoredFeedbackQuarantine,
) {
if row.status != "pending" {
return;
}
let Some(source_id) = normalized_source_id(Some(&row.source_id)) else {
return;
};
let state = states
.entry(source_id.clone())
.or_insert_with(|| SourceTrustState::new(source_id));
state.record_import();
state.record_quarantine();
}
fn apply_trust_quarantine_row(
states: &mut BTreeMap<String, SourceTrustState>,
row: &StoredTrustQuarantine,
) {
let Some(source_id) = normalized_source_id(Some(&row.source_uri)) else {
return;
};
let state = states
.entry(source_id.clone())
.or_insert_with(|| SourceTrustState::new(source_id));
state.total_imports = state.total_imports.saturating_add(row.harmful_event_count);
state.harmful_count = state.harmful_count.saturating_add(row.harmful_event_count);
if row.status == "active" {
state.record_quarantine();
}
}
fn normalized_source_id(source_id: Option<&str>) -> Option<String> {
source_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_owned)
}
fn advisory_message(advisory: &TrustAdvisory) -> String {
match advisory {
TrustAdvisory::Allow { effective_trust } => {
format!("Source is healthy with effective trust {effective_trust:.2}")
}
TrustAdvisory::Warn {
effective_trust,
message,
..
} => format!("Warning (trust {effective_trust:.2}): {message}"),
TrustAdvisory::Quarantine {
effective_trust,
negative_rate,
..
} => format!(
"Quarantined: trust {effective_trust:.2}, negative rate {:.0}%",
negative_rate * 100.0
),
TrustAdvisory::Block { reason } => format!("Blocked: {reason}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use crate::db::{CreateWorkspaceInput, UpsertTrustQuarantineInput};
use crate::models::TrustClass;
type TestResult = Result<(), String>;
fn ensure<T: std::fmt::Debug + PartialEq>(actual: T, expected: T, ctx: &str) -> TestResult {
if actual == expected {
Ok(())
} else {
Err(format!("{ctx}: expected {expected:?}, got {actual:?}"))
}
}
#[test]
fn empty_sources_produces_empty_report() -> TestResult {
let report = QuarantineReport::gather_with_sources(&[]);
ensure(report.summary.total_sources, 0, "total sources")?;
ensure(report.summary.quarantined_count, 0, "quarantined count")?;
ensure(report.summary.blocked_count, 0, "blocked count")?;
ensure(report.has_issues(), false, "no issues")
}
#[test]
fn healthy_source_not_in_issues() -> TestResult {
let state =
SourceTrustState::new("clean_source").with_trust_class(TrustClass::HumanExplicit);
let report = QuarantineReport::gather_with_sources(&[state]);
ensure(report.summary.healthy_count, 1, "healthy count")?;
ensure(report.summary.quarantined_count, 0, "quarantined count")?;
ensure(report.has_issues(), false, "no issues")
}
#[test]
fn quarantined_source_appears_in_report() -> TestResult {
let mut state =
SourceTrustState::new("bad_source").with_trust_class(TrustClass::CassEvidence);
state.record_quarantine();
state.record_quarantine();
state.record_contradiction();
state.total_imports = 5;
let report = QuarantineReport::gather_with_sources(&[state]);
ensure(report.has_issues(), true, "has issues")?;
ensure(
report.summary.quarantined_count > 0 || report.summary.at_risk_count > 0,
true,
"source flagged",
)
}
#[test]
fn blocked_source_appears_in_blocked_list() -> TestResult {
let mut state = SourceTrustState::new("terrible_source");
for _ in 0..10 {
state.record_harmful();
}
let report = QuarantineReport::gather_with_sources(&[state]);
ensure(report.summary.blocked_count, 1, "blocked count")?;
ensure(report.blocked_sources.len(), 1, "blocked list")?;
ensure(
report.blocked_sources[0].permits_import,
false,
"import not permitted",
)
}
#[test]
fn advisory_level_strings_are_stable() -> TestResult {
ensure(AdvisoryLevel::Allow.as_str(), "allow", "allow")?;
ensure(AdvisoryLevel::Warn.as_str(), "warn", "warn")?;
ensure(
AdvisoryLevel::Quarantine.as_str(),
"quarantine",
"quarantine",
)?;
ensure(AdvisoryLevel::Block.as_str(), "block", "block")
}
#[test]
fn advisory_level_is_problematic_classification() -> TestResult {
ensure(
AdvisoryLevel::Allow.is_problematic(),
false,
"allow not problematic",
)?;
ensure(
AdvisoryLevel::Warn.is_problematic(),
true,
"warn is problematic",
)?;
ensure(
AdvisoryLevel::Quarantine.is_problematic(),
true,
"quarantine is problematic",
)?;
ensure(
AdvisoryLevel::Block.is_problematic(),
true,
"block is problematic",
)
}
#[test]
fn issue_count_sums_all_problematic() -> TestResult {
let mut state1 = SourceTrustState::new("warn_source");
state1.record_quarantine();
state1.record_contradiction();
let mut state2 = SourceTrustState::new("blocked_source");
for _ in 0..10 {
state2.record_harmful();
}
let report = QuarantineReport::gather_with_sources(&[state1, state2]);
ensure(report.issue_count() >= 2, true, "at least 2 issues")
}
#[test]
fn active_only_filter_clears_non_active_summary_counts() -> TestResult {
let healthy =
SourceTrustState::new("clean_source").with_trust_class(TrustClass::HumanExplicit);
let mut blocked = SourceTrustState::new("blocked_source");
for _ in 0..10 {
blocked.record_harmful();
}
let mut report = QuarantineReport::gather_with_sources(&[healthy, blocked]);
ensure(
report.summary.total_sources,
2,
"total sources before filter",
)?;
ensure(
report.summary.healthy_count,
1,
"healthy count before filter",
)?;
ensure(
report.summary.blocked_count,
1,
"blocked count before filter",
)?;
report.filter_active_only();
ensure(report.summary.at_risk_count, 0, "at-risk count cleared")?;
ensure(report.at_risk_sources.len(), 0, "at-risk list cleared")?;
ensure(report.summary.healthy_count, 0, "healthy count cleared")?;
ensure(report.summary.total_sources, 1, "active total sources")?;
ensure(report.summary.blocked_count, 1, "blocked count retained")
}
#[test]
fn reviewed_feedback_quarantine_rows_do_not_dilute_live_feedback_rate() -> TestResult {
let mut states = BTreeMap::<String, SourceTrustState>::new();
let mut live_state = SourceTrustState::new("agent://noisy");
live_state.record_import();
live_state.record_harmful();
states.insert(live_state.source_id.clone(), live_state);
for status in ["released", "rejected"] {
apply_quarantine_row(
&mut states,
&StoredFeedbackQuarantine {
id: format!("fq_{status}"),
workspace_id: "workspace".to_owned(),
source_id: "agent://noisy".to_owned(),
target_type: "memory".to_owned(),
target_id: "mem_1".to_owned(),
signal: "harmful".to_owned(),
weight: 1.0,
source_type: "automated_check".to_owned(),
proposed_event_id: None,
recorded_at: "2026-05-06T00:00:00Z".to_owned(),
reason: "reviewed row".to_owned(),
event_reason: None,
evidence_json: None,
session_id: None,
raw_event_hash: "blake3:reviewed".to_owned(),
status: status.to_owned(),
reviewed_at: Some("2026-05-06T01:00:00Z".to_owned()),
reviewed_by: Some("tester".to_owned()),
released_feedback_event_id: None,
},
);
}
let state = states
.get("agent://noisy")
.ok_or_else(|| "source state missing".to_owned())?;
ensure(state.total_imports, 1, "reviewed rows do not add imports")?;
ensure(state.harmful_count, 1, "live harmful event retained")?;
ensure(
state.quarantine_count,
0,
"reviewed rows do not add quarantine holds",
)?;
ensure(state.negative_rate(), 1.0, "negative rate is not diluted")
}
#[test]
fn gather_for_workspace_reads_persisted_trust_quarantine() -> TestResult {
let dir = tempfile::tempdir().map_err(|error| error.to_string())?;
let workspace = dir.path().join("workspace");
fs::create_dir_all(workspace.join(".ee")).map_err(|error| error.to_string())?;
let workspace_path = workspace
.canonicalize()
.map_err(|error| error.to_string())?;
let database_path = workspace_path.join(".ee").join("ee.db");
let workspace_id = crate::core::curate::stable_workspace_id(&workspace_path);
let connection =
DbConnection::open_file(&database_path).map_err(|error| error.to_string())?;
connection.migrate().map_err(|error| error.to_string())?;
connection
.insert_workspace(
&workspace_id,
&CreateWorkspaceInput {
path: workspace_path.display().to_string(),
name: Some("quarantine test".to_owned()),
},
)
.map_err(|error| error.to_string())?;
connection
.upsert_trust_quarantine(&UpsertTrustQuarantineInput {
workspace_id,
source_uri: "cass://bad-source".to_owned(),
first_event_at: "2026-05-06T00:00:00Z".to_owned(),
last_event_at: "2026-05-06T01:00:00Z".to_owned(),
harmful_event_count: 10,
quarantined_until: Some("2026-05-07T00:00:00Z".to_owned()),
reason: "harmful evidence burst".to_owned(),
status: "active".to_owned(),
})
.map_err(|error| error.to_string())?;
let report = QuarantineReport::gather_for_workspace(&workspace);
ensure(report.storage_status.as_str(), "ready", "storage status")?;
ensure(report.summary.total_sources, 1, "total sources")?;
ensure(report.summary.blocked_count, 1, "blocked count")?;
ensure(
report.blocked_sources[0].source_id.as_str(),
"cass://bad-source",
"blocked source id",
)
}
#[test]
fn gather_for_workspace_ignores_released_trust_quarantine() -> TestResult {
let dir = tempfile::tempdir().map_err(|error| error.to_string())?;
let workspace = dir.path().join("workspace");
fs::create_dir_all(workspace.join(".ee")).map_err(|error| error.to_string())?;
let workspace_path = workspace
.canonicalize()
.map_err(|error| error.to_string())?;
let database_path = workspace_path.join(".ee").join("ee.db");
let workspace_id = crate::core::curate::stable_workspace_id(&workspace_path);
let connection =
DbConnection::open_file(&database_path).map_err(|error| error.to_string())?;
connection.migrate().map_err(|error| error.to_string())?;
connection
.insert_workspace(
&workspace_id,
&CreateWorkspaceInput {
path: workspace_path.display().to_string(),
name: Some("released quarantine test".to_owned()),
},
)
.map_err(|error| error.to_string())?;
connection
.upsert_trust_quarantine(&UpsertTrustQuarantineInput {
workspace_id,
source_uri: "cass://released-source".to_owned(),
first_event_at: "2026-05-06T00:00:00Z".to_owned(),
last_event_at: "2026-05-06T01:00:00Z".to_owned(),
harmful_event_count: 10,
quarantined_until: Some("2026-05-07T00:00:00Z".to_owned()),
reason: "reviewed and released".to_owned(),
status: "released".to_owned(),
})
.map_err(|error| error.to_string())?;
let report = QuarantineReport::gather_for_workspace(&workspace);
ensure(report.storage_status.as_str(), "ready", "storage status")?;
ensure(report.summary.total_sources, 0, "released source omitted")?;
ensure(report.summary.blocked_count, 0, "no stale block")?;
ensure(report.has_issues(), false, "released row is not live issue")
}
#[test]
fn gather_for_workspace_uninitialized_database_reports_workspace_resolution_failure()
-> TestResult {
let dir = tempfile::tempdir().map_err(|error| error.to_string())?;
let workspace = dir.path().join("workspace");
let metadata = workspace.join(".ee");
fs::create_dir_all(&metadata).map_err(|error| error.to_string())?;
fs::write(metadata.join("ee.db"), b"").map_err(|error| error.to_string())?;
let report = QuarantineReport::gather_for_workspace(&workspace);
ensure(
report.storage_status,
QuarantineStorageStatus::Unavailable,
"storage status",
)?;
let degraded = report
.degraded
.iter()
.find(|degraded| degraded.code == "quarantine_database_unreadable")
.ok_or_else(|| "missing workspace-resolution degradation".to_owned())?;
ensure(degraded.repair, "ee doctor --json", "repair")?;
ensure(
report
.degraded
.iter()
.any(|degraded| degraded.repair.contains("ee db migrate")),
false,
"schema degradations must not suggest legacy migration commands",
)
}
#[cfg(unix)]
#[test]
fn gather_for_workspace_rejects_database_under_symlinked_metadata_dir() -> TestResult {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().map_err(|error| error.to_string())?;
let workspace = dir.path().join("workspace");
fs::create_dir_all(&workspace).map_err(|error| error.to_string())?;
let outside_metadata = dir.path().join("outside-ee");
fs::create_dir_all(&outside_metadata).map_err(|error| error.to_string())?;
fs::write(outside_metadata.join("ee.db"), b"outside db")
.map_err(|error| error.to_string())?;
symlink(&outside_metadata, workspace.join(".ee")).map_err(|error| error.to_string())?;
let report = QuarantineReport::gather_for_workspace(&workspace);
ensure(
report.storage_status,
QuarantineStorageStatus::Unavailable,
"storage status",
)?;
ensure(
report
.degraded
.iter()
.any(|degraded| degraded.message.contains("symlinked component")),
true,
"symlink degradation message",
)
}
#[cfg(unix)]
#[test]
fn gather_for_workspace_rejects_symlinked_database_file() -> TestResult {
use std::os::unix::fs::symlink;
let dir = tempfile::tempdir().map_err(|error| error.to_string())?;
let workspace = dir.path().join("workspace");
let metadata = workspace.join(".ee");
fs::create_dir_all(&metadata).map_err(|error| error.to_string())?;
let outside_database = dir.path().join("outside-ee.db");
fs::write(&outside_database, b"outside db").map_err(|error| error.to_string())?;
symlink(&outside_database, metadata.join("ee.db")).map_err(|error| error.to_string())?;
let report = QuarantineReport::gather_for_workspace(&workspace);
ensure(
report.storage_status,
QuarantineStorageStatus::Unavailable,
"storage status",
)?;
ensure(
report
.degraded
.iter()
.any(|degraded| degraded.message.contains("symlinked component")),
true,
"symlink degradation message",
)
}
#[test]
fn gather_for_workspace_rejects_non_regular_database_path() -> TestResult {
let dir = tempfile::tempdir().map_err(|error| error.to_string())?;
let workspace = dir.path().join("workspace");
let database_path = workspace.join(".ee").join("ee.db");
fs::create_dir_all(&database_path).map_err(|error| error.to_string())?;
let report = QuarantineReport::gather_for_workspace(&workspace);
ensure(
report.storage_status,
QuarantineStorageStatus::Unavailable,
"storage status",
)?;
ensure(
report
.degraded
.iter()
.any(|degraded| degraded.message.contains("not a regular file")),
true,
"non-regular degradation message",
)
}
#[test]
fn version_matches_package() -> TestResult {
let report = QuarantineReport::gather();
ensure(report.version, env!("CARGO_PKG_VERSION"), "version")
}
#[test]
fn gather_without_source_store_produces_empty_report() -> TestResult {
let report = QuarantineReport::gather();
ensure(report.summary.total_sources, 0, "no tracked sources")?;
ensure(report.has_issues(), false, "empty report has no issues")
}
}