use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LogicalOperator {
And,
Or,
Not,
Implies,
Iff,
Xor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConstraintType {
Equality,
Inequality,
Greater,
Less,
Contains,
Matches,
TypeCheck,
Range,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValidationResult {
Valid,
Invalid,
Unknown,
Partial,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogicalConstraint {
pub constraint_id: String,
pub constraint_type: ConstraintType,
pub description: String,
pub left_operand: String,
pub right_operand: Option<String>,
pub operator_value: Option<String>,
pub is_required: bool,
pub error_message: String,
}
impl LogicalConstraint {
pub fn new(
constraint_id: String,
constraint_type: ConstraintType,
left_operand: String,
) -> Self {
Self {
constraint_id,
constraint_type,
description: String::new(),
left_operand,
right_operand: None,
operator_value: None,
is_required: true,
error_message: String::new(),
}
}
pub fn equality(left: &str, right: &str, description: &str) -> Self {
Self {
constraint_id: format!("eq_{}_{}", left, right),
constraint_type: ConstraintType::Equality,
description: description.to_string(),
left_operand: left.to_string(),
right_operand: Some(right.to_string()),
operator_value: None,
is_required: true,
error_message: format!("{} must equal {}", left, right),
}
}
pub fn greater(left: &str, right: &str, description: &str) -> Self {
Self {
constraint_id: format!("gt_{}_{}", left, right),
constraint_type: ConstraintType::Greater,
description: description.to_string(),
left_operand: left.to_string(),
right_operand: Some(right.to_string()),
operator_value: None,
is_required: true,
error_message: format!("{} must be greater than {}", left, right),
}
}
pub fn less(left: &str, right: &str, description: &str) -> Self {
Self {
constraint_id: format!("lt_{}_{}", left, right),
constraint_type: ConstraintType::Less,
description: description.to_string(),
left_operand: left.to_string(),
right_operand: Some(right.to_string()),
operator_value: None,
is_required: true,
error_message: format!("{} must be less than {}", left, right),
}
}
pub fn type_check(operand: &str, expected_type: &str, description: &str) -> Self {
Self {
constraint_id: format!("type_{}_{}", operand, expected_type),
constraint_type: ConstraintType::TypeCheck,
description: description.to_string(),
left_operand: operand.to_string(),
right_operand: None,
operator_value: Some(expected_type.to_string()),
is_required: true,
error_message: format!("{} must be of type {}", operand, expected_type),
}
}
pub fn with_description(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn with_error_message(mut self, message: &str) -> Self {
self.error_message = message.to_string();
self
}
pub fn with_required(mut self, required: bool) -> Self {
self.is_required = required;
self
}
}
impl fmt::Display for LogicalConstraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.constraint_type {
ConstraintType::Equality => {
write!(
f,
"{} == {}",
self.left_operand,
self.right_operand.as_deref().unwrap_or("?")
)
}
ConstraintType::Inequality => {
write!(
f,
"{} != {}",
self.left_operand,
self.right_operand.as_deref().unwrap_or("?")
)
}
ConstraintType::Greater => {
write!(
f,
"{} > {}",
self.left_operand,
self.right_operand.as_deref().unwrap_or("?")
)
}
ConstraintType::Less => {
write!(
f,
"{} < {}",
self.left_operand,
self.right_operand.as_deref().unwrap_or("?")
)
}
ConstraintType::Contains => {
write!(
f,
"{} in {}",
self.left_operand,
self.right_operand.as_deref().unwrap_or("?")
)
}
ConstraintType::Matches => {
write!(
f,
"{} matches {}",
self.left_operand,
self.operator_value.as_deref().unwrap_or("?")
)
}
ConstraintType::TypeCheck => {
write!(
f,
"type({}) == {}",
self.left_operand,
self.operator_value.as_deref().unwrap_or("?")
)
}
_ => {
if !self.description.is_empty() {
write!(f, "{}", self.description)
} else {
write!(f, "{}", self.constraint_id)
}
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceRule {
pub rule_id: String,
pub name: String,
pub description: String,
pub premises: Vec<LogicalConstraint>,
pub conclusions: Vec<String>,
pub combine_with: LogicalOperator,
pub priority: i32,
pub is_default_rule: bool,
}
impl InferenceRule {
pub fn new(rule_id: &str) -> Self {
Self {
rule_id: rule_id.to_string(),
name: rule_id.to_string(),
description: String::new(),
premises: Vec::new(),
conclusions: Vec::new(),
combine_with: LogicalOperator::And,
priority: 0,
is_default_rule: false,
}
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn with_description(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn with_premise(mut self, constraint: LogicalConstraint) -> Self {
self.premises.push(constraint);
self
}
pub fn with_conclusion(mut self, conclusion: &str) -> Self {
self.conclusions.push(conclusion.to_string());
self
}
pub fn with_operator(mut self, op: LogicalOperator) -> Self {
self.combine_with = op;
self
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn as_default(mut self) -> Self {
self.is_default_rule = true;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogicalFact {
pub fact_id: String,
pub statement: String,
pub confidence: f64,
pub derived_from: Vec<String>,
pub source: String,
pub is_persistent: bool,
}
impl LogicalFact {
pub fn new(fact_id: &str, statement: &str, confidence: f64) -> Self {
Self {
fact_id: fact_id.to_string(),
statement: statement.to_string(),
confidence,
derived_from: Vec::new(),
source: "asserted".to_string(),
is_persistent: true,
}
}
pub fn derived(fact_id: &str, statement: &str, from: &str) -> Self {
Self {
fact_id: fact_id.to_string(),
statement: statement.to_string(),
confidence: 1.0,
derived_from: vec![from.to_string()],
source: "derived".to_string(),
is_persistent: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContradictionInfo {
pub contradiction_id: String,
pub description: String,
pub fact_a: String,
pub fact_b: String,
pub source_rule: Option<String>,
pub source_constraint: Option<String>,
pub resolved: bool,
pub resolution_method: Option<String>,
pub resolution_fact: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstraintValidationResult {
pub constraint: LogicalConstraint,
pub result: ValidationResult,
pub actual_value: Option<serde_json::Value>,
pub expected_value: Option<serde_json::Value>,
pub error_message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleValidationResult {
pub rule: InferenceRule,
pub premises_satisfied: bool,
pub premise_results: Vec<ConstraintValidationResult>,
pub conclusions_derived: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeepLogicValidation {
pub is_valid: bool,
pub confidence: f64,
pub constraint_results: Vec<ConstraintValidationResult>,
pub constraints_satisfied: usize,
pub constraints_violated: usize,
pub rule_results: Vec<RuleValidationResult>,
pub rules_applied: usize,
pub facts_derived: Vec<LogicalFact>,
pub contradictions: Vec<ContradictionInfo>,
pub has_contradictions: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub recommendations: Vec<String>,
}
impl DeepLogicValidation {
pub fn new(is_valid: bool) -> Self {
Self {
is_valid,
confidence: 0.0,
constraint_results: Vec::new(),
constraints_satisfied: 0,
constraints_violated: 0,
rule_results: Vec::new(),
rules_applied: 0,
facts_derived: Vec::new(),
contradictions: Vec::new(),
has_contradictions: false,
errors: Vec::new(),
warnings: Vec::new(),
recommendations: Vec::new(),
}
}
}
type CustomPredicate =
Box<dyn Fn(&serde_json::Value, Option<&serde_json::Value>) -> bool + Send + Sync>;
pub struct ConstraintEvaluator {
custom_predicates: HashMap<String, CustomPredicate>,
}
impl ConstraintEvaluator {
pub fn new() -> Self {
Self {
custom_predicates: HashMap::new(),
}
}
pub fn register_predicate<F>(&mut self, name: &str, predicate: F)
where
F: Fn(&serde_json::Value, Option<&serde_json::Value>) -> bool + Send + Sync + 'static,
{
self.custom_predicates
.insert(name.to_string(), Box::new(predicate));
}
pub fn evaluate(
&self,
constraint: &LogicalConstraint,
data: &serde_json::Value,
) -> ConstraintValidationResult {
match self.try_evaluate(constraint, data) {
Ok((result, actual, expected)) => ConstraintValidationResult {
constraint: constraint.clone(),
result,
actual_value: actual,
expected_value: expected,
error_message: if result == ValidationResult::Valid {
String::new()
} else {
constraint.error_message.clone()
},
},
Err(e) => ConstraintValidationResult {
constraint: constraint.clone(),
result: ValidationResult::Unknown,
actual_value: None,
expected_value: None,
error_message: e,
},
}
}
fn try_evaluate(
&self,
constraint: &LogicalConstraint,
data: &serde_json::Value,
) -> Result<
(
ValidationResult,
Option<serde_json::Value>,
Option<serde_json::Value>,
),
String,
> {
let left_value = self.resolve_operand(&constraint.left_operand, data)?;
let right_value = constraint
.right_operand
.as_ref()
.map(|op| self.resolve_operand(op, data))
.transpose()?;
let result = self.evaluate_constraint_type(
constraint.constraint_type,
&left_value,
right_value.as_ref(),
constraint.operator_value.as_deref(),
)?;
Ok((
if result {
ValidationResult::Valid
} else {
ValidationResult::Invalid
},
Some(left_value),
right_value,
))
}
fn resolve_operand(
&self,
operand: &str,
data: &serde_json::Value,
) -> Result<serde_json::Value, String> {
if operand.starts_with('$') {
let path = operand.strip_prefix('$').unwrap_or(operand).split('.');
let mut value = data;
for key in path {
value = value
.get(key)
.ok_or_else(|| format!("Cannot resolve {}", operand))?;
}
Ok(value.clone())
} else if operand.starts_with('"') && operand.ends_with('"') {
Ok(serde_json::Value::String(
operand[1..operand.len() - 1].to_string(),
))
} else if let Ok(num) = operand.parse::<i64>() {
Ok(serde_json::Value::Number(num.into()))
} else if let Ok(num) = operand.parse::<f64>() {
Ok(serde_json::json!(num))
} else if operand == "true" {
Ok(serde_json::Value::Bool(true))
} else if operand == "false" {
Ok(serde_json::Value::Bool(false))
} else {
Ok(data
.get(operand)
.cloned()
.unwrap_or_else(|| serde_json::Value::String(operand.to_string())))
}
}
fn evaluate_constraint_type(
&self,
ctype: ConstraintType,
left: &serde_json::Value,
right: Option<&serde_json::Value>,
operator_value: Option<&str>,
) -> Result<bool, String> {
match ctype {
ConstraintType::Equality => Ok(right == Some(left)),
ConstraintType::Inequality => Ok(right != Some(left)),
ConstraintType::Greater => {
if let (Some(l), Some(r)) = (left.as_f64(), right.and_then(|v| v.as_f64())) {
Ok(l > r)
} else {
Err("Cannot compare non-numeric values".to_string())
}
}
ConstraintType::Less => {
if let (Some(l), Some(r)) = (left.as_f64(), right.and_then(|v| v.as_f64())) {
Ok(l < r)
} else {
Err("Cannot compare non-numeric values".to_string())
}
}
ConstraintType::Contains => {
if let Some(r) = right {
if let Some(arr) = r.as_array() {
Ok(arr.contains(left))
} else if let Some(obj) = r.as_object() {
Ok(obj.contains_key(left.as_str().unwrap_or("")))
} else {
Ok(false)
}
} else {
Ok(false)
}
}
ConstraintType::Matches => {
if let Some(pattern) = operator_value {
if let Some(s) = left.as_str() {
Ok(s.contains(pattern))
} else {
Ok(false)
}
} else {
Ok(false)
}
}
ConstraintType::TypeCheck => {
if let Some(expected_type) = operator_value {
let matches = match expected_type.to_lowercase().as_str() {
"str" | "string" => left.is_string(),
"int" | "integer" => {
left.is_number()
&& left.as_f64().unwrap_or(0.0).fract().abs() < f64::EPSILON
}
"float" | "number" => left.is_number(),
"bool" | "boolean" => left.is_boolean(),
"array" | "list" => left.is_array(),
"object" | "dict" => left.is_object(),
_ => false,
};
Ok(matches)
} else {
Ok(false)
}
}
ConstraintType::Range => {
if let Some(range_str) = operator_value {
if let Some((min_str, max_str)) = range_str.split_once(',') {
if let (Ok(min_val), Ok(max_val), Some(val)) = (
min_str.trim().parse::<f64>(),
max_str.trim().parse::<f64>(),
left.as_f64(),
) {
Ok(val >= min_val && val <= max_val)
} else {
Err("Invalid range format".to_string())
}
} else {
Err("Range must be in format 'min,max'".to_string())
}
} else {
Ok(false)
}
}
ConstraintType::Custom => {
if let Some(pred_name) = operator_value {
if let Some(predicate) = self.custom_predicates.get(pred_name) {
Ok(predicate(left, right))
} else {
Err(format!("Unknown custom predicate: {}", pred_name))
}
} else {
Err("Custom constraint requires operator_value".to_string())
}
}
}
}
}
impl Default for ConstraintEvaluator {
fn default() -> Self {
Self::new()
}
}
pub struct InferenceEngine {
max_iterations: usize,
#[allow(dead_code)]
detect_contradictions: bool,
rules: Vec<InferenceRule>,
facts: HashMap<String, LogicalFact>,
evaluator: ConstraintEvaluator,
}
impl InferenceEngine {
pub fn new(max_iterations: usize, detect_contradictions: bool) -> Self {
Self {
max_iterations,
detect_contradictions,
rules: Vec::new(),
facts: HashMap::new(),
evaluator: ConstraintEvaluator::new(),
}
}
pub fn add_rule(&mut self, rule: InferenceRule) {
self.rules.push(rule);
self.rules.sort_by(|a, b| b.priority.cmp(&a.priority));
}
pub fn add_fact(&mut self, fact: LogicalFact) {
self.facts.insert(fact.fact_id.clone(), fact);
}
pub fn assert_fact(&mut self, statement: &str, confidence: f64) -> LogicalFact {
let fact_id = format!("fact_{}", self.facts.len());
let fact = LogicalFact::new(&fact_id, statement, confidence);
self.facts.insert(fact_id.clone(), fact.clone());
fact
}
pub fn infer(&mut self, data: &serde_json::Value) -> Vec<LogicalFact> {
let mut derived = Vec::new();
let mut iteration = 0;
while iteration < self.max_iterations {
let mut new_facts = Vec::new();
for rule in &self.rules {
let result = self.apply_rule(rule, data);
if result.premises_satisfied {
for conclusion in &result.conclusions_derived {
let fact_id = format!("derived_{}", self.facts.len() + new_facts.len());
let fact = LogicalFact::derived(&fact_id, conclusion, &rule.rule_id);
new_facts.push(fact);
}
}
}
if new_facts.is_empty() {
break;
}
for fact in &new_facts {
self.facts.insert(fact.fact_id.clone(), fact.clone());
derived.push(fact.clone());
}
iteration += 1;
}
derived
}
fn apply_rule(&self, rule: &InferenceRule, data: &serde_json::Value) -> RuleValidationResult {
let mut premise_results = Vec::new();
let mut all_satisfied = true;
for premise in &rule.premises {
let result = self.evaluator.evaluate(premise, data);
premise_results.push(result.clone());
match rule.combine_with {
LogicalOperator::And => {
if result.result != ValidationResult::Valid {
all_satisfied = false;
break;
}
}
LogicalOperator::Or => {
if result.result == ValidationResult::Valid {
all_satisfied = true;
break;
} else {
all_satisfied = false;
}
}
_ => {
if result.result != ValidationResult::Valid {
all_satisfied = false;
}
}
}
}
let conclusions = if all_satisfied {
rule.conclusions.clone()
} else {
Vec::new()
};
RuleValidationResult {
rule: rule.clone(),
premises_satisfied: all_satisfied,
premise_results,
conclusions_derived: conclusions,
}
}
}
pub struct ContradictionDetector {
contradiction_patterns: Vec<(String, String, String)>,
}
impl ContradictionDetector {
pub fn new() -> Self {
Self {
contradiction_patterns: vec![
(
"is true".to_string(),
"is false".to_string(),
"Direct truth contradiction".to_string(),
),
(
"exists".to_string(),
"does not exist".to_string(),
"Existence contradiction".to_string(),
),
(
"is valid".to_string(),
"is invalid".to_string(),
"Validity contradiction".to_string(),
),
(
"should".to_string(),
"should not".to_string(),
"Normative contradiction".to_string(),
),
(
"must".to_string(),
"must not".to_string(),
"Obligation contradiction".to_string(),
),
(
"always".to_string(),
"never".to_string(),
"Temporal contradiction".to_string(),
),
(
"all".to_string(),
"none".to_string(),
"Quantifier contradiction".to_string(),
),
],
}
}
pub fn detect(&self, facts: &[LogicalFact]) -> Vec<ContradictionInfo> {
let mut contradictions = Vec::new();
let statements: Vec<(String, String)> = facts
.iter()
.map(|f| (f.fact_id.clone(), f.statement.to_lowercase()))
.collect();
for (i, (id_a, stmt_a)) in statements.iter().enumerate() {
for (id_b, stmt_b) in statements.iter().skip(i + 1) {
if let Some(description) = self.check_contradiction(stmt_a, stmt_b) {
contradictions.push(ContradictionInfo {
contradiction_id: format!("contradiction_{}", contradictions.len()),
description,
fact_a: id_a.clone(),
fact_b: id_b.clone(),
source_rule: None,
source_constraint: None,
resolved: false,
resolution_method: None,
resolution_fact: None,
});
}
}
}
contradictions
}
fn check_contradiction(&self, stmt_a: &str, stmt_b: &str) -> Option<String> {
for (pattern_a, pattern_b, description) in &self.contradiction_patterns {
if ((stmt_a.contains(pattern_a) && stmt_b.contains(pattern_b))
|| (stmt_a.contains(pattern_b) && stmt_b.contains(pattern_a)))
&& self.same_subject(stmt_a, stmt_b)
{
return Some(description.clone());
}
}
None
}
fn same_subject(&self, stmt_a: &str, stmt_b: &str) -> bool {
let words_a: std::collections::HashSet<&str> = stmt_a.split_whitespace().collect();
let words_b: std::collections::HashSet<&str> = stmt_b.split_whitespace().collect();
let common: std::collections::HashSet<&str> =
["the", "a", "an", "is", "are", "was", "were", "be", "been"]
.iter()
.cloned()
.collect();
let words_a: std::collections::HashSet<&str> =
words_a.difference(&common).cloned().collect();
let words_b: std::collections::HashSet<&str> =
words_b.difference(&common).cloned().collect();
if words_a.is_empty() || words_b.is_empty() {
return false;
}
let overlap = words_a.intersection(&words_b).count();
let min_len = words_a.len().min(words_b.len());
overlap as f64 / min_len as f64 > 0.3
}
}
impl Default for ContradictionDetector {
fn default() -> Self {
Self::new()
}
}
pub struct DeepLogicValidator {
constraints: Vec<LogicalConstraint>,
inference_engine: InferenceEngine,
evaluator: ConstraintEvaluator,
contradiction_detector: ContradictionDetector,
}
impl DeepLogicValidator {
pub fn new() -> Self {
Self {
constraints: Vec::new(),
inference_engine: InferenceEngine::new(100, true),
evaluator: ConstraintEvaluator::new(),
contradiction_detector: ContradictionDetector::new(),
}
}
pub fn add_constraint(&mut self, constraint: LogicalConstraint) {
self.constraints.push(constraint);
}
pub fn add_rule(&mut self, rule: InferenceRule) {
self.inference_engine.add_rule(rule);
}
pub fn validate(
&mut self,
data: &serde_json::Value,
_reasoning_output: Option<&serde_json::Value>,
) -> DeepLogicValidation {
let mut result = DeepLogicValidation::new(true);
for constraint in &self.constraints {
let constraint_result = self.evaluator.evaluate(constraint, data);
result.constraint_results.push(constraint_result.clone());
match constraint_result.result {
ValidationResult::Valid => {
result.constraints_satisfied += 1;
}
ValidationResult::Invalid => {
result.constraints_violated += 1;
if constraint.is_required {
result.is_valid = false;
result.errors.push(constraint_result.error_message);
}
}
_ => {
result.warnings.push(format!(
"Constraint {} could not be evaluated",
constraint.constraint_id
));
}
}
}
let derived_facts = self.inference_engine.infer(data);
result.facts_derived = derived_facts.clone();
result.rules_applied = self.inference_engine.rules.len();
let all_facts: Vec<LogicalFact> = self.inference_engine.facts.values().cloned().collect();
let contradictions = self.contradiction_detector.detect(&all_facts);
result.contradictions = contradictions.clone();
result.has_contradictions = !contradictions.is_empty();
if result.has_contradictions {
result.is_valid = false;
result
.errors
.push("Contradictions detected in reasoning".to_string());
}
let total_constraints = result.constraints_satisfied + result.constraints_violated;
if total_constraints > 0 {
result.confidence = result.constraints_satisfied as f64 / total_constraints as f64;
}
result
}
}
impl Default for DeepLogicValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constraint_equality() {
let constraint = LogicalConstraint::equality("age", "18", "Age must be 18");
assert_eq!(constraint.constraint_type, ConstraintType::Equality);
assert_eq!(constraint.left_operand, "age");
assert_eq!(constraint.right_operand, Some("18".to_string()));
}
#[test]
fn test_constraint_display() {
let constraint = LogicalConstraint::equality("x", "y", "Test");
assert!(constraint.to_string().contains("=="));
}
#[test]
fn test_inference_rule() {
let rule = InferenceRule::new("test_rule")
.with_name("Test Rule")
.with_premise(LogicalConstraint::greater("age", "17", "Must be adult"))
.with_conclusion("is_adult")
.with_priority(10);
assert_eq!(rule.rule_id, "test_rule");
assert_eq!(rule.name, "Test Rule");
assert_eq!(rule.premises.len(), 1);
assert_eq!(rule.conclusions.len(), 1);
assert_eq!(rule.priority, 10);
}
#[test]
fn test_constraint_evaluator_equality() {
let evaluator = ConstraintEvaluator::new();
let constraint = LogicalConstraint::equality("age", "18", "Age must be 18");
let data = serde_json::json!({ "age": 18 });
let result = evaluator.evaluate(&constraint, &data);
assert_eq!(result.result, ValidationResult::Valid);
}
#[test]
fn test_constraint_evaluator_greater() {
let evaluator = ConstraintEvaluator::new();
let constraint = LogicalConstraint::greater("age", "17", "Must be adult");
let data = serde_json::json!({ "age": 18 });
let result = evaluator.evaluate(&constraint, &data);
assert_eq!(result.result, ValidationResult::Valid);
}
#[test]
fn test_inference_engine() {
let mut engine = InferenceEngine::new(10, false);
let rule = InferenceRule::new("adult_rule")
.with_premise(LogicalConstraint::greater("age", "17", "Must be adult"))
.with_conclusion("is_adult");
engine.add_rule(rule);
let data = serde_json::json!({ "age": 18 });
let derived = engine.infer(&data);
assert!(!derived.is_empty());
assert!(derived[0].statement == "is_adult");
}
#[test]
fn test_contradiction_detector() {
let detector = ContradictionDetector::new();
let facts = vec![
LogicalFact::new("f1", "X is true", 1.0),
LogicalFact::new("f2", "X is false", 1.0),
];
let contradictions = detector.detect(&facts);
assert!(!contradictions.is_empty());
}
#[test]
fn test_deep_logic_validator() {
let mut validator = DeepLogicValidator::new();
validator.add_constraint(LogicalConstraint::equality("age", "18", "Age must be 18"));
let data = serde_json::json!({ "age": 18 });
let result = validator.validate(&data, None);
assert!(result.is_valid);
assert_eq!(result.constraints_satisfied, 1);
}
}