use super::*;
#[test]
fn cancelled_query_returns_err() {
use std::sync::atomic::{AtomicBool, Ordering};
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal("gerku", "danlu"));
let flag = std::sync::Arc::new(AtomicBool::new(true));
kb.set_cancel_flag(flag.clone());
let result = kb.query_entailment_inner(make_query("alis", "danlu"));
assert!(
result.is_err(),
"cancelled query must return Err, got {result:?}"
);
assert!(
result.unwrap_err().to_lowercase().contains("cancel"),
"cancellation error should mention 'cancel'"
);
flag.store(false, Ordering::Relaxed);
kb.clear_cancel_flag();
assert!(query(&kb, make_query("alis", "danlu")));
}
#[test]
fn test_existential_import_presupposition_basic() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn clean_core_flag_off_mints_no_presupposition_witness() {
let kb = new_kb();
kb.set_existential_import(false);
assert!(!kb.is_existential_import());
assert_buf(&kb, make_universal("gerku", "danlu"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
assert!(
!query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
),
"clean-core (existential import off) must not mint a presupposition witness"
);
}
#[test]
fn test_existential_import_presupposition_consequent() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"danlu",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_existential_import_presupposition_conjunction() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
let mut nodes = Vec::new();
let p1 = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let p2 = pred(
&mut nodes,
"danlu",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let conj = and(&mut nodes, p1, p2);
let root = exists(&mut nodes, "x", conj);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_existential_import_presupposition_with_real_entity() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query(&kb, make_query("alis", "danlu")));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"danlu",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
let results = query_find(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert_eq!(
results.len(),
1,
"only the real entity is enumerable, not the presupposition phantom: {results:?}"
);
assert!(
results[0]
.iter()
.any(|b| matches!(&b.term, LogicalTerm::Constant(c) if c == "alis")),
"the surviving witness is alis: {results:?}"
);
}
#[test]
fn test_prenex_universal_asserts_no_presupposition_witness() {
let kb = new_kb();
let mut nodes = Vec::new();
let restrict = pred(
&mut nodes,
"broda",
vec![
LogicalTerm::Variable("da".to_string()),
LogicalTerm::Unspecified,
],
);
let body = pred(
&mut nodes,
"brodb",
vec![
LogicalTerm::Variable("da".to_string()),
LogicalTerm::Unspecified,
],
);
let neg = not(&mut nodes, restrict);
let disj = or(&mut nodes, neg, body);
let root = forall(&mut nodes, "da", disj);
assert_buf(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
let mut q = Vec::new();
let qb = pred(
&mut q,
"broda",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let qroot = exists(&mut q, "x", qb);
assert!(
query_false(
&kb,
LogicBuffer {
nodes: q,
roots: vec![qroot]
}
),
"a prenex universal must NOT assert a presupposition witness"
);
let kb2 = new_kb();
assert_buf(&kb2, make_universal("gerku", "danlu"));
let mut q2 = Vec::new();
let qb2 = pred(
&mut q2,
"gerku",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let qroot2 = exists(&mut q2, "x", qb2);
assert!(
query(
&kb2,
LogicBuffer {
nodes: q2,
roots: vec![qroot2]
}
),
"a description universal must still assert its presupposition witness"
);
}
#[test]
fn test_bare_universal_asserts_no_presupposition_witness() {
let kb = new_kb();
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"brodb",
vec![
LogicalTerm::Variable("da".to_string()),
LogicalTerm::Unspecified,
],
);
let root = forall(&mut nodes, "da", body);
assert_buf(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
let mut q = Vec::new();
let qb = pred(
&mut q,
"brodb",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let qroot = exists(&mut q, "x", qb);
assert!(
query_false(
&kb,
LogicBuffer {
nodes: q,
roots: vec![qroot]
}
),
"a bare (restrictor-less) universal must NOT assert a presupposition witness"
);
}
#[test]
fn test_existential_import_presupposition_transitive() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_universal("danlu", "xanlu"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"xanlu",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_existential_import_presupposition_no_false_positives() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"mlatu",
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
assert!(query_false(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_native_rule_transitive_chain() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_universal("danlu", "xanlu"));
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query(&kb, make_query("alis", "xanlu")));
}
#[test]
fn test_native_rule_multiple_entities() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("bob", "gerku"));
assert_buf(&kb, make_universal("gerku", "danlu"));
assert!(query(&kb, make_query("alis", "danlu")));
assert!(query(&kb, make_query("bob", "danlu")));
}
#[test]
fn test_native_rule_negated_universal() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
let mut nodes = Vec::new();
let restrict = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let body_pred = pred(
&mut nodes,
"danlu",
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let neg_body = not(&mut nodes, body_pred);
let neg_restrict = not(&mut nodes, restrict);
let disj = or(&mut nodes, neg_restrict, neg_body);
let root = forall(&mut nodes, "_v0", disj);
let result = kb.assert_fact_inner(
LogicBuffer {
nodes,
roots: vec![root],
},
String::new(),
);
assert!(
result.is_err(),
"negated-conclusion universal must be rejected (fail-closed), got {result:?}"
);
assert!(query_false(&kb, make_query("alis", "danlu")));
}
#[test]
fn test_naf_negated_antecedent_untraced() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_negated_antecedent_rule());
assert!(
query(&kb, make_query("alis", "danlu")),
"danlu(alis) should hold: gerku true and mlatu unprovable (NAF)"
);
assert_buf(&kb, make_assertion("alis", "mlatu"));
assert!(
matches!(
query_result(&kb, make_query("alis", "danlu")),
QueryResult::False
),
"danlu(alis) should be FALSE once mlatu(alis) is asserted"
);
}
#[test]
fn test_naf_negated_antecedent_traced() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_negated_antecedent_rule());
let (result, trace) = kb
.query_entailment_with_proof_inner(make_query("alis", "danlu"))
.unwrap();
assert!(
result.is_true(),
"traced verdict should be TRUE before mlatu"
);
assert!(
trace.has_naf_dependency(),
"proof should record a negation-as-failure dependency for ¬mlatu"
);
assert_buf(&kb, make_assertion("alis", "mlatu"));
let (result2, _trace2) = kb
.query_entailment_with_proof_inner(make_query("alis", "danlu"))
.unwrap();
assert!(
result2.is_false(),
"traced verdict should be FALSE after mlatu(alis) is asserted"
);
}
#[test]
fn test_naf_negated_antecedent_rule_shape() {
let kb = new_kb();
assert_buf(&kb, make_negated_antecedent_rule());
let inner = kb.inner.borrow();
let rule = inner
.universal_rules
.values()
.flatten()
.find(|r| r.typed_conclusions.iter().any(|c| c.relation() == "danlu"))
.expect("a rule concluding danlu should be registered");
let cond_rels: Vec<&str> = rule.typed_conditions.iter().map(|c| c.relation()).collect();
assert_eq!(
cond_rels,
vec!["gerku", "mlatu"],
"antecedent And should flatten into two conditions"
);
assert_eq!(
rule.negated_condition_indices,
vec![1],
"mlatu (index 1) should be the negated condition"
);
}
#[test]
fn rule_firing_conformance() {
let kb = new_kb();
assert_buf(&kb, make_negated_antecedent_rule());
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(
query(&kb, make_query("alis", "danlu")),
"gerku(alis) present and mlatu(alis) absent → the rule fires → danlu(alis)"
);
assert!(
query_false(&kb, make_query("bob", "danlu")),
"head instantiated to the matched entity: danlu(bob) must NOT be derived"
);
let (ok, trace) = query_with_proof(&kb, make_query("alis", "danlu"));
assert!(ok);
assert!(
trace.has_naf_dependency(),
"the ¬mlatu condition must be a negation-as-failure dependency"
);
assert!(
trace.steps.iter().any(|s| matches!(
&s.rule,
ProofRule::Derived { fact, .. } if fact.contains("danlu") && fact.contains("alis")
)),
"a Derived step must record the instantiated head danlu(alis)"
);
let kb2 = new_kb();
assert_buf(&kb2, make_negated_antecedent_rule());
assert_buf(&kb2, make_assertion("alis", "gerku"));
assert_buf(&kb2, make_assertion("alis", "mlatu"));
assert!(
query_false(&kb2, make_query("alis", "danlu")),
"mlatu(alis) present → the negated condition fails → no firing (no fabrication)"
);
let kb3 = new_kb();
assert_buf(&kb3, make_negated_antecedent_rule());
assert!(
query_false(&kb3, make_query("alis", "danlu")),
"gerku(alis) missing → the positive condition is undischarged → no firing (no fabrication)"
);
}
#[test]
fn test_native_rule_duplicate_rule_no_panic() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query(&kb, make_query("alis", "danlu")));
}
#[test]
fn test_query_result_false_for_missing_fact() {
let kb = new_kb();
let result = query_result(&kb, make_query("alis", "gerku"));
assert!(matches!(result, QueryResult::False));
}
#[test]
fn test_query_result_unknown_for_cycle_cut() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_universal("danlu", "gerku"));
let result = query_result(&kb, make_query("alis", "gerku"));
assert!(matches!(
result,
QueryResult::Unknown(UnknownReason::CycleCut)
));
let (result, _) = kb
.query_entailment_with_proof_inner(make_query("alis", "gerku"))
.unwrap();
assert!(matches!(
result,
QueryResult::Unknown(UnknownReason::CycleCut)
));
}
#[test]
fn test_query_result_resource_exceeded_for_depth_limit() {
let kb = new_kb();
kb.inner.borrow_mut().max_chain_depth = 1;
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_universal("danlu", "xanlu"));
let result = query_result(&kb, make_query("alis", "xanlu"));
assert!(matches!(
result,
QueryResult::ResourceExceeded(ResourceKind::Depth)
));
let (result, _) = kb
.query_entailment_with_proof_inner(make_query("alis", "xanlu"))
.unwrap();
assert!(matches!(
result,
QueryResult::ResourceExceeded(ResourceKind::Depth)
));
}