use recursive_kernel_core::*;
use stack_ids::{ConstraintId, KernelRunId, OperatorId, OracleSliceId, ScopeKey, SyndromeId};
fn valid_operator_metadata() -> OperatorMetadata {
OperatorMetadata {
operator_id: OperatorId::new("test.operator.v1"),
name: "test_operator".to_string(),
contract: OperatorContract {
inputs: vec!["input_a".into()],
outputs: vec!["output_b".into()],
exactness: ExactnessClass::Exact,
convergence: ConvergenceKind::AcyclicSinglePass,
stop_rule: StopRule::AcyclicCompletion,
degradation: DegradationBehavior::AdvisoryOnly,
},
}
}
#[test]
fn test_operator_validate_empty_name_fails() {
let mut meta = valid_operator_metadata();
meta.name = "".to_string();
let result = meta.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "operator name must not be empty");
}
#[test]
fn test_operator_validate_empty_inputs_fails() {
let mut meta = valid_operator_metadata();
meta.contract.inputs = vec![];
let result = meta.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "operator inputs must not be empty");
}
#[test]
fn test_operator_validate_empty_outputs_fails() {
let mut meta = valid_operator_metadata();
meta.contract.outputs = vec![];
let result = meta.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "operator outputs must not be empty");
}
#[test]
fn test_operator_validate_success() {
let meta = valid_operator_metadata();
assert!(meta.validate().is_ok());
}
#[test]
fn test_kernel_run_authority_class() {
let run_without_oracle = KernelRun {
run_id: KernelRunId::new("run-alpha"),
policy_version: "policy-v2".into(),
scope_key: ScopeKey::namespace_only("ns-test"),
operator_ids: vec![OperatorId::new(CONSTRAINT_COMPILER_OPERATOR_ID)],
oracle_slice_id: None,
degradation_active: false,
};
assert_eq!(
run_without_oracle.authority_class(),
ArtifactAuthorityClass::NonAuthoritativeDerived
);
let run_with_oracle = KernelRun {
run_id: KernelRunId::new("run-beta"),
policy_version: "policy-v3".into(),
scope_key: ScopeKey::namespace_only("ns-test"),
operator_ids: vec![
OperatorId::new(CONSTRAINT_COMPILER_OPERATOR_ID),
OperatorId::new(RECURSIVE_MESSAGE_PASSING_OPERATOR_ID),
],
oracle_slice_id: Some(OracleSliceId::new("oracle-1")),
degradation_active: true,
};
assert_eq!(
run_with_oracle.authority_class(),
ArtifactAuthorityClass::NonAuthoritativeDerived
);
}
#[test]
fn test_syndrome_serde_roundtrip() {
let syndrome = Syndrome {
syndrome_id: SyndromeId::new("syn-001"),
signature: "constraint_violation::node_42".to_string(),
blocked_by_degradation: true,
};
let json = serde_json::to_string(&syndrome).unwrap();
let deserialized: Syndrome = serde_json::from_str(&json).unwrap();
assert_eq!(syndrome, deserialized);
}
#[test]
fn test_residual_artifact_monotone_flag() {
let residual_mono = ResidualArtifact {
constraint_id: ConstraintId::new("c:mono"),
residual_micros: 500,
monotone_nonincreasing: true,
};
let json = serde_json::to_string(&residual_mono).unwrap();
let deserialized: ResidualArtifact = serde_json::from_str(&json).unwrap();
assert_eq!(residual_mono, deserialized);
assert!(deserialized.monotone_nonincreasing);
let residual_non_mono = ResidualArtifact {
constraint_id: ConstraintId::new("c:nonmono"),
residual_micros: 1200,
monotone_nonincreasing: false,
};
let json = serde_json::to_string(&residual_non_mono).unwrap();
let deserialized: ResidualArtifact = serde_json::from_str(&json).unwrap();
assert!(!deserialized.monotone_nonincreasing);
}
#[test]
fn test_witness_artifact_multiple_constraints() {
let witness = WitnessArtifact {
witness_id: "wit-multi".into(),
target_node_id: "node-99".into(),
supporting_constraint_ids: vec![
ConstraintId::new("constraint:1"),
ConstraintId::new("constraint:2"),
ConstraintId::new("constraint:3"),
],
belief_micros: 950_000,
};
let json = serde_json::to_string(&witness).unwrap();
let deserialized: WitnessArtifact = serde_json::from_str(&json).unwrap();
assert_eq!(witness, deserialized);
assert_eq!(deserialized.supporting_constraint_ids.len(), 3);
assert_eq!(
deserialized.supporting_constraint_ids[0],
ConstraintId::new("constraint:1")
);
assert_eq!(
deserialized.supporting_constraint_ids[2],
ConstraintId::new("constraint:3")
);
}
#[test]
fn test_certificate_with_oracle_slice() {
let cert = CertificateArtifact {
certificate_id: "cert-with-oracle".into(),
certified_node_id: "node-certified".into(),
satisfied_constraint_ids: vec![ConstraintId::new("c:sat1")],
oracle_slice_id: Some(OracleSliceId::new("oracle-slice-42")),
};
let json = serde_json::to_string(&cert).unwrap();
let deserialized: CertificateArtifact = serde_json::from_str(&json).unwrap();
assert_eq!(cert, deserialized);
assert!(deserialized.oracle_slice_id.is_some());
assert_eq!(
deserialized.oracle_slice_id.unwrap(),
OracleSliceId::new("oracle-slice-42")
);
}
#[test]
fn test_certificate_without_oracle_slice() {
let cert = CertificateArtifact {
certificate_id: "cert-no-oracle".into(),
certified_node_id: "node-standalone".into(),
satisfied_constraint_ids: vec![ConstraintId::new("c:sat1"), ConstraintId::new("c:sat2")],
oracle_slice_id: None,
};
let json = serde_json::to_string(&cert).unwrap();
let deserialized: CertificateArtifact = serde_json::from_str(&json).unwrap();
assert_eq!(cert, deserialized);
assert!(deserialized.oracle_slice_id.is_none());
let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
if let Some(val) = json_value.get("oracle_slice_id") {
assert!(val.is_null());
}
}
#[test]
fn test_refutation_result_flip_witness_found() {
let result = KernelRefutationResult {
target_node_id: "node-flip".into(),
outcome: KernelRefutationOutcome::FlipWitnessFound,
witness: Some(PerturbationWitness {
changed_node_ids: vec!["node-a".into(), "node-b".into()],
changed_constraint_ids: vec![ConstraintId::new("c:changed")],
budget_used: 42,
}),
notes: vec!["flip detected at iteration 7".into()],
};
assert!(matches!(
result.outcome,
KernelRefutationOutcome::FlipWitnessFound
));
assert!(result.witness.is_some());
let witness = result.witness.as_ref().unwrap();
assert_eq!(witness.changed_node_ids.len(), 2);
assert_eq!(witness.budget_used, 42);
}
#[test]
fn test_refutation_result_no_flip_within_budget() {
let result = KernelRefutationResult {
target_node_id: "node-stable".into(),
outcome: KernelRefutationOutcome::NoFlipFoundWithinBudget,
witness: None,
notes: vec!["exhausted budget of 100 perturbations".into()],
};
assert!(matches!(
result.outcome,
KernelRefutationOutcome::NoFlipFoundWithinBudget
));
assert!(result.witness.is_none());
assert_eq!(result.notes.len(), 1);
}
#[test]
fn test_refutation_result_unsupported() {
let result = KernelRefutationResult {
target_node_id: "node-unsupported".into(),
outcome: KernelRefutationOutcome::Unsupported,
witness: None,
notes: vec![],
};
assert!(matches!(
result.outcome,
KernelRefutationOutcome::Unsupported
));
assert!(result.witness.is_none());
assert!(result.notes.is_empty());
}
#[test]
fn test_perturbation_witness_serde_roundtrip() {
let witness = PerturbationWitness {
changed_node_ids: vec!["n1".into(), "n2".into(), "n3".into()],
changed_constraint_ids: vec![ConstraintId::new("c:p1"), ConstraintId::new("c:p2")],
budget_used: 17,
};
let json = serde_json::to_string(&witness).unwrap();
let deserialized: PerturbationWitness = serde_json::from_str(&json).unwrap();
assert_eq!(witness, deserialized);
}
#[test]
fn test_calibration_report_blocked() {
let report = CalibrationReport {
nuisance_node_ids: vec!["nuisance-1".into()],
caveats: vec!["stale calibration data".into()],
blocked: true,
};
let json = serde_json::to_string(&report).unwrap();
let deserialized: CalibrationReport = serde_json::from_str(&json).unwrap();
assert_eq!(report, deserialized);
assert!(deserialized.blocked);
assert_eq!(deserialized.caveats.len(), 1);
}
#[test]
fn test_calibration_report_not_blocked() {
let report = CalibrationReport {
nuisance_node_ids: vec![],
caveats: vec![],
blocked: false,
};
let json = serde_json::to_string(&report).unwrap();
let deserialized: CalibrationReport = serde_json::from_str(&json).unwrap();
assert_eq!(report, deserialized);
assert!(!deserialized.blocked);
assert!(deserialized.nuisance_node_ids.is_empty());
assert!(deserialized.caveats.is_empty());
}
#[test]
fn test_operator_validate_whitespace_only_name_fails() {
let mut meta = valid_operator_metadata();
meta.name = " ".to_string();
let result = meta.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "operator name must not be empty");
}