use serde::{Deserialize, Serialize};
use crate::collectors::TELEMETRY_SCHEMA_VERSION;
use crate::host_adapter::{ConformanceHarness, ConformanceViolation, HostAdapter};
pub const MAX_SCHEMA_VERSION_LAG: u8 = 1;
const REPLAY_CONTRACT_SANITY_TESTS: &str =
"cargo test -p frankensearch-core contract_sanity::tests -- --nocapture";
const REPLAY_ADAPTER_CONFORMANCE_TESTS: &str =
"cargo test -p frankensearch-core host_adapter::tests -- --nocapture";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContractSanityReport {
pub core_schema_version: u8,
pub adapters_checked: usize,
pub adapters_passed: usize,
pub adapter_results: Vec<AdapterContractResult>,
pub passed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdapterContractResult {
pub adapter_id: String,
pub host_project: String,
pub adapter_schema_version: u8,
pub passed: bool,
pub compatibility: CompatibilityStatus,
pub violations: Vec<ConformanceViolation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompatibilityStatus {
Exact,
Compatible {
lag: u8,
},
Deprecated {
lag: u8,
},
TooNew {
ahead: u8,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ViolationSeverity {
Warning,
Error,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContractViolationDiagnostic {
pub adapter_id: String,
pub host_project: String,
pub compatibility: CompatibilityStatus,
pub reason_code: String,
pub field: String,
pub message: String,
pub severity: ViolationSeverity,
pub replay_command: String,
}
impl ContractSanityReport {
#[must_use]
pub fn diagnostics(&self) -> Vec<ContractViolationDiagnostic> {
let mut diagnostics = Vec::new();
for result in &self.adapter_results {
for violation in &result.violations {
diagnostics.push(ContractViolationDiagnostic {
adapter_id: result.adapter_id.clone(),
host_project: result.host_project.clone(),
compatibility: result.compatibility.clone(),
reason_code: violation.code.clone(),
field: violation.field.clone(),
message: violation.message.clone(),
severity: classify_violation_severity(&violation.code, &result.compatibility),
replay_command: replay_command_for_reason(&violation.code, &result.adapter_id),
});
}
}
diagnostics.sort_by(|left, right| {
left.adapter_id
.cmp(&right.adapter_id)
.then(left.reason_code.cmp(&right.reason_code))
.then(left.field.cmp(&right.field))
});
diagnostics
}
}
pub struct ContractSanityChecker {
harness: ConformanceHarness,
}
impl Default for ContractSanityChecker {
fn default() -> Self {
Self::new(ConformanceHarness::default())
}
}
impl ContractSanityChecker {
#[must_use]
pub const fn new(harness: ConformanceHarness) -> Self {
Self { harness }
}
#[must_use]
pub fn check_adapter(&self, adapter: &dyn HostAdapter) -> AdapterContractResult {
let identity = adapter.identity();
let mut violations = self.harness.validate_identity(&identity);
let compatibility = classify_version_against(
TELEMETRY_SCHEMA_VERSION,
identity.telemetry_schema_version,
MAX_SCHEMA_VERSION_LAG,
);
match &compatibility {
CompatibilityStatus::Deprecated { lag } => {
violations.push(ConformanceViolation {
code: "contract.schema.deprecated".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: format!(
"schema v{} is {} version(s) behind current v{} \
(max lag: {MAX_SCHEMA_VERSION_LAG})",
identity.telemetry_schema_version, lag, TELEMETRY_SCHEMA_VERSION
),
});
}
CompatibilityStatus::TooNew { ahead } => {
violations.push(ConformanceViolation {
code: "contract.schema.too_new".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: format!(
"adapter schema v{} is {} version(s) ahead of core v{} \
— core must be upgraded first",
identity.telemetry_schema_version, ahead, TELEMETRY_SCHEMA_VERSION
),
});
}
CompatibilityStatus::Compatible { lag } => {
if *lag > 0 {
violations.push(ConformanceViolation {
code: "contract.schema.lagging".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: format!(
"adapter schema v{} lags core v{} by {} version(s) \
— within compatibility window but should be updated",
identity.telemetry_schema_version, TELEMETRY_SCHEMA_VERSION, lag
),
});
}
}
CompatibilityStatus::Exact => {}
}
let compatible_lagging = matches!(compatibility, CompatibilityStatus::Compatible { .. });
let has_hard_violations = violations.iter().any(|violation| {
if compatible_lagging {
!matches!(
violation.code.as_str(),
"contract.schema.lagging" | "adapter.identity.schema_version_mismatch"
)
} else {
true
}
});
let passed = !has_hard_violations;
AdapterContractResult {
adapter_id: identity.adapter_id,
host_project: identity.host_project,
adapter_schema_version: identity.telemetry_schema_version,
passed,
compatibility,
violations,
}
}
#[must_use]
pub fn check_all(&self, adapters: &[&dyn HostAdapter]) -> ContractSanityReport {
let adapter_results: Vec<_> = adapters
.iter()
.map(|adapter| self.check_adapter(*adapter))
.collect();
let adapters_passed = adapter_results.iter().filter(|r| r.passed).count();
let passed = adapters_passed == adapter_results.len();
ContractSanityReport {
core_schema_version: TELEMETRY_SCHEMA_VERSION,
adapters_checked: adapter_results.len(),
adapters_passed,
adapter_results,
passed,
}
}
}
fn classify_violation_severity(
reason_code: &str,
compatibility: &CompatibilityStatus,
) -> ViolationSeverity {
match reason_code {
"contract.schema.lagging" => ViolationSeverity::Warning,
"adapter.identity.schema_version_mismatch"
if matches!(compatibility, CompatibilityStatus::Compatible { .. }) =>
{
ViolationSeverity::Warning
}
_ => ViolationSeverity::Error,
}
}
#[must_use]
pub fn replay_command_for_reason(reason_code: &str, adapter_id: &str) -> String {
let adapter_prefix = format!("FRANKENSEARCH_HOST_ADAPTER={adapter_id}");
if matches!(
reason_code,
"contract.schema.lagging"
| "contract.schema.deprecated"
| "contract.schema.too_new"
| "adapter.identity.canonical_pair_mismatch"
| "adapter.identity.schema_version_mismatch"
) {
format!("{adapter_prefix} {REPLAY_CONTRACT_SANITY_TESTS}")
} else if reason_code.starts_with("adapter.") {
format!("{adapter_prefix} {REPLAY_ADAPTER_CONFORMANCE_TESTS}")
} else {
format!("{adapter_prefix} cargo test -p frankensearch-core -- --nocapture")
}
}
#[must_use]
pub const fn classify_version(adapter_version: u8) -> CompatibilityStatus {
classify_version_against(
TELEMETRY_SCHEMA_VERSION,
adapter_version,
MAX_SCHEMA_VERSION_LAG,
)
}
#[must_use]
#[allow(clippy::comparison_chain)] pub const fn classify_version_against(
core_schema_version: u8,
adapter_version: u8,
max_schema_version_lag: u8,
) -> CompatibilityStatus {
if adapter_version == core_schema_version {
CompatibilityStatus::Exact
} else if adapter_version > core_schema_version {
CompatibilityStatus::TooNew {
ahead: adapter_version - core_schema_version,
}
} else {
let lag = core_schema_version - adapter_version;
if lag <= max_schema_version_lag {
CompatibilityStatus::Compatible { lag }
} else {
CompatibilityStatus::Deprecated { lag }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::SearchResult;
use crate::host_adapter::{AdapterIdentity, AdapterLifecycleEvent};
#[derive(Debug)]
struct StubAdapter {
identity: AdapterIdentity,
}
impl StubAdapter {
fn with_version(version: u8) -> Self {
Self::with_identity("test-adapter", "test-project", version)
}
fn with_identity(adapter_id: &str, host_project: &str, version: u8) -> Self {
Self {
identity: AdapterIdentity {
adapter_id: adapter_id.to_owned(),
adapter_version: "0.1.0".to_owned(),
host_project: host_project.to_owned(),
runtime_role: None,
instance_uuid: None,
telemetry_schema_version: version,
redaction_policy_version: "v1".to_owned(),
},
}
}
}
impl HostAdapter for StubAdapter {
fn identity(&self) -> AdapterIdentity {
self.identity.clone()
}
fn emit_telemetry(
&self,
_envelope: &crate::collectors::TelemetryEnvelope,
) -> SearchResult<()> {
Ok(())
}
fn on_lifecycle_event(&self, _event: &AdapterLifecycleEvent) -> SearchResult<()> {
Ok(())
}
}
#[test]
fn exact_version_passes() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION);
let result = checker.check_adapter(&adapter);
assert!(result.passed);
assert_eq!(result.compatibility, CompatibilityStatus::Exact);
}
#[test]
fn compatible_lagging_version_passes_with_warning() {
if TELEMETRY_SCHEMA_VERSION == 0 {
return;
}
let checker = ContractSanityChecker::default();
let adapter = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION - 1);
let result = checker.check_adapter(&adapter);
assert!(result.passed);
assert_eq!(
result.compatibility,
CompatibilityStatus::Compatible { lag: 1 }
);
assert!(
result
.violations
.iter()
.any(|v| v.code == "contract.schema.lagging")
);
}
#[test]
fn deprecated_version_fails() {
if TELEMETRY_SCHEMA_VERSION <= MAX_SCHEMA_VERSION_LAG {
return;
}
let checker = ContractSanityChecker::default();
let old_version = TELEMETRY_SCHEMA_VERSION - MAX_SCHEMA_VERSION_LAG - 1;
let adapter = StubAdapter::with_version(old_version);
let result = checker.check_adapter(&adapter);
assert!(!result.passed);
assert!(matches!(
result.compatibility,
CompatibilityStatus::Deprecated { .. }
));
}
#[test]
fn too_new_version_fails() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION + 1);
let result = checker.check_adapter(&adapter);
assert!(!result.passed);
assert_eq!(
result.compatibility,
CompatibilityStatus::TooNew { ahead: 1 }
);
}
#[test]
fn check_all_reports_summary() {
let checker = ContractSanityChecker::default();
let good = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION);
let bad = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION + 5);
let adapters: Vec<&dyn HostAdapter> = vec![&good, &bad];
let report = checker.check_all(&adapters);
assert_eq!(report.adapters_checked, 2);
assert_eq!(report.adapters_passed, 1);
assert!(!report.passed);
}
#[test]
fn check_all_passes_when_all_exact() {
let checker = ContractSanityChecker::default();
let a1 = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION);
let a2 = StubAdapter::with_version(TELEMETRY_SCHEMA_VERSION);
let adapters: Vec<&dyn HostAdapter> = vec![&a1, &a2];
let report = checker.check_all(&adapters);
assert!(report.passed);
assert_eq!(report.adapters_passed, 2);
}
#[test]
fn classify_version_exact() {
assert_eq!(
classify_version(TELEMETRY_SCHEMA_VERSION),
CompatibilityStatus::Exact
);
}
#[test]
fn classify_version_too_new() {
assert_eq!(
classify_version(TELEMETRY_SCHEMA_VERSION + 3),
CompatibilityStatus::TooNew { ahead: 3 }
);
}
#[test]
fn classify_version_compatible() {
if TELEMETRY_SCHEMA_VERSION == 0 {
return;
}
assert_eq!(
classify_version(TELEMETRY_SCHEMA_VERSION - 1),
CompatibilityStatus::Compatible { lag: 1 }
);
}
#[test]
fn classify_version_against_supports_drift_simulation() {
assert_eq!(
classify_version_against(3, 3, 1),
CompatibilityStatus::Exact
);
assert_eq!(
classify_version_against(3, 2, 1),
CompatibilityStatus::Compatible { lag: 1 }
);
assert_eq!(
classify_version_against(3, 1, 1),
CompatibilityStatus::Deprecated { lag: 2 }
);
assert_eq!(
classify_version_against(3, 4, 1),
CompatibilityStatus::TooNew { ahead: 1 }
);
}
#[test]
fn empty_adapter_id_fails_identity_check() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter {
identity: AdapterIdentity {
adapter_id: String::new(),
adapter_version: "0.1.0".to_owned(),
host_project: "test".to_owned(),
runtime_role: None,
instance_uuid: None,
telemetry_schema_version: TELEMETRY_SCHEMA_VERSION,
redaction_policy_version: "v1".to_owned(),
},
};
let result = checker.check_adapter(&adapter);
assert!(!result.passed);
assert!(
result
.violations
.iter()
.any(|v| v.code == "adapter.identity.missing_adapter_id")
);
}
#[test]
fn wrong_redaction_policy_fails() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter {
identity: AdapterIdentity {
adapter_id: "test".to_owned(),
adapter_version: "0.1.0".to_owned(),
host_project: "test".to_owned(),
runtime_role: None,
instance_uuid: None,
telemetry_schema_version: TELEMETRY_SCHEMA_VERSION,
redaction_policy_version: "v99".to_owned(),
},
};
let result = checker.check_adapter(&adapter);
assert!(!result.passed);
assert!(
result
.violations
.iter()
.any(|v| v.code == "adapter.identity.redaction_policy_mismatch")
);
}
#[test]
fn canonical_identity_pair_match_passes() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter::with_identity("xf-host-adapter", "xf", TELEMETRY_SCHEMA_VERSION);
let result = checker.check_adapter(&adapter);
assert!(result.passed);
assert!(
!result
.violations
.iter()
.any(|v| v.code == "adapter.identity.canonical_pair_mismatch")
);
}
#[test]
fn canonical_host_with_wrong_adapter_id_fails() {
let checker = ContractSanityChecker::default();
let adapter =
StubAdapter::with_identity("cass-host-adapter", "xf", TELEMETRY_SCHEMA_VERSION);
let result = checker.check_adapter(&adapter);
assert!(!result.passed);
assert!(result.violations.iter().any(|v| {
v.code == "adapter.identity.canonical_pair_mismatch" && v.field == "identity.adapter_id"
}));
}
#[test]
fn canonical_adapter_id_with_wrong_host_fails() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter::with_identity(
"mcp-agent-mail-host-adapter",
"custom-mail-host",
TELEMETRY_SCHEMA_VERSION,
);
let result = checker.check_adapter(&adapter);
assert!(!result.passed);
assert!(result.violations.iter().any(|v| {
v.code == "adapter.identity.canonical_pair_mismatch"
&& v.field == "identity.host_project"
}));
}
#[test]
fn unknown_identity_pair_remains_allowed_for_future_hosts() {
let checker = ContractSanityChecker::default();
let adapter = StubAdapter::with_identity(
"custom-host-adapter",
"custom_host",
TELEMETRY_SCHEMA_VERSION,
);
let result = checker.check_adapter(&adapter);
assert!(result.passed);
assert!(
!result
.violations
.iter()
.any(|v| v.code == "adapter.identity.canonical_pair_mismatch")
);
}
#[test]
fn contract_report_serde_roundtrip() {
let report = ContractSanityReport {
core_schema_version: TELEMETRY_SCHEMA_VERSION,
adapters_checked: 1,
adapters_passed: 1,
adapter_results: vec![AdapterContractResult {
adapter_id: "test".to_owned(),
host_project: "proj".to_owned(),
adapter_schema_version: TELEMETRY_SCHEMA_VERSION,
passed: true,
compatibility: CompatibilityStatus::Exact,
violations: vec![],
}],
passed: true,
};
let json = serde_json::to_string(&report).expect("serialize");
let back: ContractSanityReport = serde_json::from_str(&json).expect("deserialize");
assert_eq!(report, back);
}
#[test]
fn check_all_with_empty_list() {
let checker = ContractSanityChecker::default();
let adapters: Vec<&dyn HostAdapter> = vec![];
let report = checker.check_all(&adapters);
assert!(report.passed);
assert_eq!(report.adapters_checked, 0);
}
#[test]
fn diagnostics_are_deterministic_with_replay_commands() {
let report = ContractSanityReport {
core_schema_version: TELEMETRY_SCHEMA_VERSION,
adapters_checked: 2,
adapters_passed: 1,
adapter_results: vec![
AdapterContractResult {
adapter_id: "xf-host-adapter".to_owned(),
host_project: "xf".to_owned(),
adapter_schema_version: TELEMETRY_SCHEMA_VERSION + 1,
passed: false,
compatibility: CompatibilityStatus::TooNew { ahead: 1 },
violations: vec![ConformanceViolation {
code: "contract.schema.too_new".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: "too new".to_owned(),
}],
},
AdapterContractResult {
adapter_id: "cass-host-adapter".to_owned(),
host_project: "cass".to_owned(),
adapter_schema_version: TELEMETRY_SCHEMA_VERSION,
passed: false,
compatibility: CompatibilityStatus::Exact,
violations: vec![ConformanceViolation {
code: "adapter.identity.redaction_policy_mismatch".to_owned(),
field: "identity.redaction_policy_version".to_owned(),
message: "bad redaction policy".to_owned(),
}],
},
],
passed: false,
};
let diagnostics = report.diagnostics();
assert_eq!(diagnostics.len(), 2);
assert_eq!(diagnostics[0].adapter_id, "cass-host-adapter");
assert_eq!(
diagnostics[0].severity,
ViolationSeverity::Error,
"redaction mismatch is a hard violation"
);
assert!(
diagnostics[0]
.replay_command
.contains("host_adapter::tests"),
"adapter-level violations should replay host adapter conformance tests"
);
assert_eq!(diagnostics[1].adapter_id, "xf-host-adapter");
assert_eq!(diagnostics[1].reason_code, "contract.schema.too_new");
assert!(
diagnostics[1]
.replay_command
.contains("contract_sanity::tests"),
"schema drift violations should replay contract sanity tests"
);
}
#[test]
fn canonical_pair_mismatch_uses_contract_replay_and_error_severity() {
let report = ContractSanityReport {
core_schema_version: TELEMETRY_SCHEMA_VERSION,
adapters_checked: 1,
adapters_passed: 0,
adapter_results: vec![AdapterContractResult {
adapter_id: "xf-host-adapter".to_owned(),
host_project: "wrong-host".to_owned(),
adapter_schema_version: TELEMETRY_SCHEMA_VERSION,
passed: false,
compatibility: CompatibilityStatus::Exact,
violations: vec![ConformanceViolation {
code: "adapter.identity.canonical_pair_mismatch".to_owned(),
field: "identity.host_project".to_owned(),
message: "adapter_id 'xf-host-adapter' expects host_project 'xf'".to_owned(),
}],
}],
passed: false,
};
let diagnostics = report.diagnostics();
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].severity, ViolationSeverity::Error);
assert!(
diagnostics[0]
.replay_command
.contains("contract_sanity::tests"),
"canonical pair mismatch must replay contract_sanity tests"
);
assert!(
!diagnostics[0]
.replay_command
.contains("host_adapter::tests"),
"canonical pair mismatch should not route to host_adapter tests"
);
}
#[test]
fn lagging_schema_mismatch_is_warning_in_diagnostics() {
let report = ContractSanityReport {
core_schema_version: TELEMETRY_SCHEMA_VERSION,
adapters_checked: 1,
adapters_passed: 1,
adapter_results: vec![AdapterContractResult {
adapter_id: "ops-host-adapter".to_owned(),
host_project: "ops".to_owned(),
adapter_schema_version: TELEMETRY_SCHEMA_VERSION.saturating_sub(1),
passed: true,
compatibility: CompatibilityStatus::Compatible { lag: 1 },
violations: vec![
ConformanceViolation {
code: "adapter.identity.schema_version_mismatch".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: "expected schema mismatch warning".to_owned(),
},
ConformanceViolation {
code: "contract.schema.lagging".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: "lagging in window".to_owned(),
},
],
}],
passed: true,
};
let diagnostics = report.diagnostics();
assert_eq!(diagnostics.len(), 2);
assert!(
diagnostics
.iter()
.all(|diag| diag.severity == ViolationSeverity::Warning)
);
}
#[test]
fn two_host_adapter_drift_scenario_emits_actionable_diagnostics() {
let checker = ContractSanityChecker::default();
let cass =
StubAdapter::with_identity("cass-host-adapter", "cass", TELEMETRY_SCHEMA_VERSION);
let xf = StubAdapter::with_identity("xf-host-adapter", "xf", TELEMETRY_SCHEMA_VERSION + 1);
let adapters: Vec<&dyn HostAdapter> = vec![&cass, &xf];
let report = checker.check_all(&adapters);
assert!(!report.passed);
let diagnostics = report.diagnostics();
assert!(
diagnostics
.iter()
.any(|diag| diag.reason_code == "contract.schema.too_new")
);
assert!(
diagnostics
.iter()
.all(|diag| !diag.replay_command.is_empty())
);
}
#[test]
fn replay_command_mapping_uses_expected_harnesses() {
let schema_cmd = replay_command_for_reason("contract.schema.too_new", "xf-host-adapter");
assert!(schema_cmd.contains("contract_sanity::tests"));
assert!(schema_cmd.contains("FRANKENSEARCH_HOST_ADAPTER=xf-host-adapter"));
let pair_cmd = replay_command_for_reason(
"adapter.identity.canonical_pair_mismatch",
"xf-host-adapter",
);
assert!(pair_cmd.contains("contract_sanity::tests"));
assert!(pair_cmd.contains("FRANKENSEARCH_HOST_ADAPTER=xf-host-adapter"));
let adapter_cmd = replay_command_for_reason(
"adapter.identity.redaction_policy_mismatch",
"cass-host-adapter",
);
assert!(adapter_cmd.contains("host_adapter::tests"));
assert!(adapter_cmd.contains("FRANKENSEARCH_HOST_ADAPTER=cass-host-adapter"));
}
#[test]
fn compatibility_status_serde_roundtrip() {
for status in [
CompatibilityStatus::Exact,
CompatibilityStatus::Compatible { lag: 1 },
CompatibilityStatus::Deprecated { lag: 3 },
CompatibilityStatus::TooNew { ahead: 2 },
] {
let json = serde_json::to_string(&status).unwrap();
let back: CompatibilityStatus = serde_json::from_str(&json).unwrap();
assert_eq!(status, back);
}
}
#[test]
fn violation_severity_serde_roundtrip() {
for severity in [ViolationSeverity::Warning, ViolationSeverity::Error] {
let json = serde_json::to_string(&severity).unwrap();
let back: ViolationSeverity = serde_json::from_str(&json).unwrap();
assert_eq!(severity, back);
}
}
#[test]
fn contract_violation_diagnostic_debug_clone_eq() {
let diag = ContractViolationDiagnostic {
adapter_id: "test-adapter".to_owned(),
host_project: "test-project".to_owned(),
compatibility: CompatibilityStatus::Exact,
reason_code: "test.code".to_owned(),
field: "test.field".to_owned(),
message: "test message".to_owned(),
severity: ViolationSeverity::Warning,
replay_command: "cargo test".to_owned(),
};
let debug = format!("{diag:?}");
assert!(debug.contains("ContractViolationDiagnostic"));
let cloned = diag.clone();
assert_eq!(diag, cloned);
}
#[test]
fn contract_violation_diagnostic_serde_roundtrip() {
let diag = ContractViolationDiagnostic {
adapter_id: "xf".to_owned(),
host_project: "xf-project".to_owned(),
compatibility: CompatibilityStatus::TooNew { ahead: 1 },
reason_code: "contract.schema.too_new".to_owned(),
field: "identity.telemetry_schema_version".to_owned(),
message: "too new".to_owned(),
severity: ViolationSeverity::Error,
replay_command: "cargo test".to_owned(),
};
let json = serde_json::to_string(&diag).unwrap();
let back: ContractViolationDiagnostic = serde_json::from_str(&json).unwrap();
assert_eq!(diag, back);
}
#[test]
fn classify_violation_severity_all_cases() {
assert_eq!(
classify_violation_severity("contract.schema.lagging", &CompatibilityStatus::Exact),
ViolationSeverity::Warning
);
assert_eq!(
classify_violation_severity(
"adapter.identity.schema_version_mismatch",
&CompatibilityStatus::Compatible { lag: 1 }
),
ViolationSeverity::Warning
);
assert_eq!(
classify_violation_severity(
"adapter.identity.schema_version_mismatch",
&CompatibilityStatus::Deprecated { lag: 2 }
),
ViolationSeverity::Error
);
assert_eq!(
classify_violation_severity(
"contract.schema.deprecated",
&CompatibilityStatus::Deprecated { lag: 2 }
),
ViolationSeverity::Error
);
assert_eq!(
classify_violation_severity(
"contract.schema.too_new",
&CompatibilityStatus::TooNew { ahead: 1 }
),
ViolationSeverity::Error
);
assert_eq!(
classify_violation_severity("unknown.reason", &CompatibilityStatus::Exact),
ViolationSeverity::Error
);
}
#[test]
fn replay_command_unknown_reason_falls_back() {
let cmd = replay_command_for_reason("completely.unknown.code", "my-adapter");
assert!(cmd.contains("FRANKENSEARCH_HOST_ADAPTER=my-adapter"));
assert!(cmd.contains("cargo test -p frankensearch-core"));
assert!(!cmd.contains("contract_sanity::tests"));
assert!(!cmd.contains("host_adapter::tests"));
}
#[test]
fn replay_command_for_lagging_and_deprecated() {
let lagging = replay_command_for_reason("contract.schema.lagging", "ops-adapter");
assert!(lagging.contains("contract_sanity::tests"));
let deprecated = replay_command_for_reason("contract.schema.deprecated", "old-adapter");
assert!(deprecated.contains("contract_sanity::tests"));
}
#[test]
fn diagnostics_empty_report_returns_empty() {
let report = ContractSanityReport {
core_schema_version: TELEMETRY_SCHEMA_VERSION,
adapters_checked: 0,
adapters_passed: 0,
adapter_results: vec![],
passed: true,
};
assert!(report.diagnostics().is_empty());
}
#[test]
fn classify_version_against_zero_lag_requires_exact() {
assert_eq!(
classify_version_against(5, 5, 0),
CompatibilityStatus::Exact
);
assert_eq!(
classify_version_against(5, 4, 0),
CompatibilityStatus::Deprecated { lag: 1 }
);
assert_eq!(
classify_version_against(5, 6, 0),
CompatibilityStatus::TooNew { ahead: 1 }
);
}
}