use serde_json::Value;
use super::tmp_kb;
use crate::kb::{AppraiseParams, Situation};
use crate::{KnowledgeBase, RecallParams, RecordParams};
const KEYS: &str = "stage,error_class,file_type";
fn recall_context_score(kb: &KnowledgeBase, query: &str, id: &str) -> f64 {
let r = kb
.recall(RecallParams {
query,
budget: 6000,
trace: false,
source: "sdk",
..Default::default()
})
.unwrap();
r.knowledge
.iter()
.find(|c| c.get("id").and_then(Value::as_str) == Some(id))
.and_then(|c| c.get("_context_score").and_then(Value::as_f64))
.unwrap_or(f64::NAN)
}
fn add_active(kb: &KnowledgeBase, content: &str, trigger: &str) -> String {
kb.add(content, "note", Some(trigger), None, "manual", None)
.unwrap()
}
fn appraise_query(kb: &KnowledgeBase, q: &str) -> crate::Verdict {
let actions: Vec<String> = vec![];
kb.appraise(AppraiseParams {
situation: Situation {
query: Some(q),
recent_actions: &actions,
..Default::default()
},
trace: true,
source: "sdk",
..Default::default()
})
.unwrap()
}
#[test]
fn defaults_do_not_abstain() {
let (kb, _f) = tmp_kb();
add_active(
&kb,
"Use cargo build --release to build innate",
"build the binary",
);
let v = appraise_query(&kb, "how do I build the release binary");
assert!(!v.abstained, "默认参数下不应弃权");
assert!(v.abstain_reason.is_none());
}
#[test]
fn gate3_sparse_evidence_abstains_when_activated() {
let (kb, f) = tmp_kb();
add_active(
&kb,
"Use cargo build --release to build innate",
"build the binary",
);
kb.storage.set_meta("appraise.min_evidence", "3").unwrap();
drop(kb);
let kb = KnowledgeBase::open(f.path()).unwrap();
let v = appraise_query(&kb, "how do I build the release binary");
assert!(v.abstained, "证据稀疏应弃权");
assert_eq!(v.abstain_reason, Some(crate::AbstainReason::SparseEvidence));
assert_eq!(v.confidence, 0.0, "弃权时 confidence 归零");
}
#[test]
fn gate4_conflicted_abstains_when_ceiling_zero() {
let (kb, f) = tmp_kb();
add_active(&kb, "Use cargo build --release", "build the binary");
add_active(&kb, "Run cargo test for the suite", "build the binary test");
add_active(&kb, "cargo fmt formats the code", "build the binary format");
kb.storage
.set_meta("appraise.conflict_ceiling", "0.0")
.unwrap();
kb.storage.set_meta("appraise.min_strength", "0.0").unwrap(); drop(kb);
let kb = KnowledgeBase::open(f.path()).unwrap();
let v = appraise_query(&kb, "build binary");
if v.contributors.len() >= 2 {
assert!(v.abstained);
assert_eq!(v.abstain_reason, Some(crate::AbstainReason::Conflicted));
}
}
#[test]
fn scheme_b_verdict_log_emit_and_backfill() {
let (kb, _f) = tmp_kb();
add_active(
&kb,
"Prefer immediate transactions for sqlite writes",
"sqlite writes",
);
let v = appraise_query(&kb, "should I batch these sqlite writes");
let rows = kb
.storage
.query_chunks_params(
"SELECT COUNT(*) AS cnt FROM verdict_log WHERE trace_id=?",
rusqlite::params![v.trace_id],
)
.unwrap();
assert_eq!(
rows[0].get("cnt").and_then(Value::as_i64),
Some(1),
"appraise 必写一条 verdict_log"
);
let pending = kb
.storage
.query_chunks_params(
"SELECT COUNT(*) AS cnt FROM verdict_log WHERE trace_id=? AND outcome_observed_at IS NULL",
rusqlite::params![v.trace_id],
)
.unwrap();
assert_eq!(
pending[0].get("cnt").and_then(Value::as_i64),
Some(1),
"回填前 outcome 为空"
);
kb.record(RecordParams {
trace_id: &v.trace_id,
outcome: Some("fail"),
source: "sdk",
..Default::default()
})
.unwrap();
let prov = kb
.storage
.query_chunks_params(
"SELECT outcome_provenance AS p FROM verdict_log WHERE trace_id=?",
rusqlite::params![v.trace_id],
)
.unwrap();
assert_eq!(
prov[0].get("p").and_then(Value::as_str),
Some("observed"),
"record 实际结果回填为 observed"
);
}
#[test]
fn scheme_h_heeded_caution_is_censored_not_calibrated() {
let (kb, _f) = tmp_kb();
add_active(
&kb,
"Avoid: running cargo build without --release in CI",
"ci build flags",
);
let v = appraise_query(&kb, "should I run cargo build in CI");
kb.record(RecordParams {
trace_id: &v.trace_id,
outcome: Some("ok"),
verdict_heeded: true,
source: "sdk",
..Default::default()
})
.unwrap();
let prov = kb
.storage
.query_chunks_params(
"SELECT outcome_provenance AS p FROM verdict_log WHERE trace_id=?",
rusqlite::params![v.trace_id],
)
.unwrap();
assert_eq!(
prov[0].get("p").and_then(Value::as_str),
Some("counterfactual_censored"),
"被采纳的警告应记为反事实审查,不记 observed"
);
assert!(
kb.storage.verdict_calibration_samples().unwrap().is_empty(),
"counterfactual_censored 不得进入校准样本"
);
}
#[test]
fn neutral_and_mixed_verdicts_excluded_from_calibration() {
let (kb, _f) = tmp_kb();
let now = "2026-06-01T00:00:00.000Z";
for (vid, valence) in [("v-aff", "affirm"), ("v-neu", "neutral"), ("v-mix", "mixed")] {
kb.storage
.insert_verdict_log(vid, vid, "sig", Some(valence), Some(0.7), 0.7, Some("medium"), None, now)
.unwrap();
kb.storage
.backfill_verdict_outcome(vid, -1.0, "observed", now)
.unwrap();
}
let samples = kb.storage.verdict_calibration_samples().unwrap();
assert_eq!(samples.len(), 1, "只有 affirm/caution 进入校准样本");
let (strength, conf, hit) = samples[0];
assert!((strength - 0.7).abs() < 1e-9 && (conf - 0.7).abs() < 1e-9);
assert_eq!(hit, 1.0);
}
#[test]
fn scheme_c_verdict_derived_evidence_excluded_from_confidence() {
let (kb, _f) = tmp_kb();
let id = add_active(&kb, "Some procedural knowledge", "do the thing");
kb.storage
.upsert_confidence_evidence(
"ev-vd-1",
Some("trace-x"),
&id,
"outcome_ok",
1.0,
1.0,
"verdict-derived push",
None,
"2026-06-01T00:00:00.000Z",
"verdict_derived",
)
.unwrap();
kb.storage
.upsert_confidence_evidence(
"ev-obs-1",
Some("trace-y"),
&id,
"outcome_ok",
1.0,
1.0,
"observed",
None,
"2026-06-01T00:00:02.000Z",
"observed",
)
.unwrap();
let used = kb.storage.confidence_evidence_for_chunk(&id).unwrap();
assert_eq!(
used.len(),
1,
"verdict_derived 证据不得参与 confidence 重算"
);
assert_eq!(kb.storage.observed_outcome_count(&id).unwrap(), 1);
}
#[test]
fn scheme_d_base_rate_prior_param_roundtrip() {
let (kb, f) = tmp_kb();
add_active(&kb, "Use cargo build --release", "build the binary");
kb.storage.set_meta("intuition.prior_m", "20.0").unwrap();
kb.storage.set_meta("intuition.base_rate", "0.1").unwrap();
drop(kb);
let kb = KnowledgeBase::open(f.path()).unwrap();
let v = appraise_query(&kb, "how do I build the release binary");
assert!(v.strength >= 0.0 && v.strength <= 1.0);
}
#[test]
fn scheme_d_prior_decoupled_from_recall() {
let (kb, f) = tmp_kb();
let id = add_active(
&kb,
"Use cargo build --release to build innate",
"build the binary",
);
let query = "how do I build the release binary";
let r = kb
.recall(RecallParams {
query,
budget: 6000,
trace: true,
source: "sdk",
..Default::default()
})
.unwrap();
kb.record(RecordParams {
trace_id: &r.trace_id,
outcome: Some("fail"),
used: Some(std::slice::from_ref(&id)),
source: "sdk",
..Default::default()
})
.unwrap();
let cs1 = recall_context_score(&kb, query, &id);
let ck = Situation::from_query(query).context_key(KEYS);
let neutral = kb
.storage
.context_scores_batch(&[&id], &ck, 2.0, 0.5)
.unwrap();
let intuition = kb
.storage
.context_scores_batch(&[&id], &ck, 50.0, 0.01)
.unwrap();
let (nv, iv) = (
neutral.get(&id).copied().unwrap_or(0.0),
intuition.get(&id).copied().unwrap_or(0.0),
);
assert!(
(nv - iv).abs() > 1e-6,
"基率先验应实际改变 context 分:neutral={nv} intuition={iv}"
);
kb.storage.set_meta("intuition.base_rate", "0.01").unwrap();
kb.storage.set_meta("intuition.prior_m", "50.0").unwrap();
drop(kb);
let kb = KnowledgeBase::open(f.path()).unwrap();
let cs2 = recall_context_score(&kb, query, &id);
assert!(
(cs1 - cs2).abs() < 1e-9,
"recall 的 context 分不得受 intuition.base_rate 影响:before={cs1} after={cs2}"
);
}
#[test]
fn scheme_e_calibration_map_applied_on_emit() {
let (kb, _f) = tmp_kb();
add_active(
&kb,
"Use cargo build --release to build innate",
"build the binary",
);
let raw = appraise_query(&kb, "how do I build the release binary");
assert!(!raw.abstained);
let raw_conf = raw.confidence;
let mut buckets: Vec<(f64, f64, f64, i64)> = Vec::new();
for b in 0..10 {
buckets.push((b as f64 / 10.0, (b as f64 + 1.0) / 10.0, 0.05, 5));
}
kb.storage
.replace_calibration_map(&buckets, "2026-06-01T00:00:00.000Z")
.unwrap();
let calibrated = appraise_query(&kb, "how do I build the release binary");
if !calibrated.abstained && raw_conf > 0.05 {
assert!(
calibrated.confidence <= raw_conf,
"校准映射应把 confidence 拉向实际命中率:raw={raw_conf} cal={}",
calibrated.confidence
);
assert!((calibrated.confidence - 0.05).abs() < 1e-6);
}
}