car-memgine 0.15.0

Memgine — graph-based memory engine for Common Agent Runtime
Documentation
//! Graduated confidence scoring for facts.
//!
//! Replaces binary valid/invalid with 4 levels:
//! - **Definite**: policy/system source, executive authority
//! - **Likely**: high authority, corroborated by multiple sources
//! - **Uncertain**: unverified/draft, single low-authority source
//! - **Stale**: exceeded type-aware TTL
//!
//! Inspired by StateBench's Honcho-inspired confidence module.

use chrono::{DateTime, Duration, Utc};

/// Graduated confidence level for a fact.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Confidence {
    /// Policy/system source, executive authority — highest trust.
    Definite,
    /// High authority or corroborated by multiple sources.
    Likely,
    /// Unverified, draft, or single low-authority source.
    Uncertain,
    /// Exceeded type-aware TTL — needs refresh.
    Stale,
}

impl Confidence {
    /// Display annotation for context assembly.
    /// Returns empty string for Likely (the default, no noise needed).
    pub fn annotation(&self) -> &'static str {
        match self {
            Confidence::Definite => " [CONFIRMED]",
            Confidence::Likely => "",
            Confidence::Uncertain => " [UNVERIFIED]",
            Confidence::Stale => " [STALE]",
        }
    }

    /// Numeric weight for scoring (0.0 to 1.0).
    pub fn weight(&self) -> f64 {
        match self {
            Confidence::Definite => 1.0,
            Confidence::Likely => 0.8,
            Confidence::Uncertain => 0.4,
            Confidence::Stale => 0.1,
        }
    }
}

impl std::fmt::Display for Confidence {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Confidence::Definite => write!(f, "definite"),
            Confidence::Likely => write!(f, "likely"),
            Confidence::Uncertain => write!(f, "uncertain"),
            Confidence::Stale => write!(f, "stale"),
        }
    }
}

/// TTL in days by source type.
fn ttl_days_for_source(source_type: &str) -> i64 {
    match source_type {
        "policy" => 365,
        "system" => 180,
        "external" => 90,
        "user" | "peer" => 90,
        _ => 90,
    }
}

/// TTL in days for constraint types (overrides source-based TTL).
fn ttl_days_for_constraint(constraint_type: &str) -> i64 {
    match constraint_type {
        "policy" => 365,
        "deadline" => 30,
        "budget" => 90,
        "capacity" => 30,
        _ => 90,
    }
}

/// Infer confidence level from fact metadata.
///
/// Rules (checked in order):
/// 1. Expired TTL → Stale
/// 2. Hypothetical/draft scope → Uncertain
/// 3. Policy/executive + constraint → Definite
/// 4. Policy/system source → Definite
/// 5. High authority (policy/executive/manager) → Likely
/// 6. Unverified/peer source → Uncertain
/// 7. Default → Likely
pub fn infer_confidence(
    authority: &str,
    source_type: &str,
    scope: &str,
    is_constraint: bool,
    created_at: DateTime<Utc>,
    now: Option<DateTime<Utc>>,
) -> Confidence {
    let now = now.unwrap_or_else(Utc::now);

    // 1. TTL-based staleness
    let ttl = if is_constraint {
        // Use constraint-type TTL (inferred from source)
        ttl_days_for_constraint(source_type)
    } else {
        ttl_days_for_source(source_type)
    };
    if now - created_at > Duration::days(ttl) {
        return Confidence::Stale;
    }

    // 2. Hypothetical/draft always uncertain
    if scope == "hypothetical" || scope == "draft" {
        return Confidence::Uncertain;
    }

    // 3. Policy/executive constraints are definite
    if is_constraint && (authority == "policy" || authority == "executive") {
        return Confidence::Definite;
    }

    // 4. Policy and system sources
    if source_type == "policy" || source_type == "system" {
        return Confidence::Definite;
    }

    // 5. High authority
    if authority == "policy" || authority == "executive" || authority == "manager" {
        return Confidence::Likely;
    }

    // 6. Unverified/peer source
    if authority == "unverified" || authority == "peer" {
        return Confidence::Uncertain;
    }

    // 7. Default
    Confidence::Likely
}

#[cfg(test)]
mod tests {
    use super::*;

    fn now() -> DateTime<Utc> {
        Utc::now()
    }

    #[test]
    fn policy_source_is_definite() {
        let c = infer_confidence("policy", "policy", "global", false, now(), None);
        assert_eq!(c, Confidence::Definite);
    }

    #[test]
    fn system_source_is_definite() {
        let c = infer_confidence("system", "system", "global", false, now(), None);
        assert_eq!(c, Confidence::Definite);
    }

    #[test]
    fn executive_constraint_is_definite() {
        let c = infer_confidence("executive", "user", "global", true, now(), None);
        assert_eq!(c, Confidence::Definite);
    }

    #[test]
    fn manager_is_likely() {
        let c = infer_confidence("manager", "user", "global", false, now(), None);
        assert_eq!(c, Confidence::Likely);
    }

    #[test]
    fn peer_is_uncertain() {
        let c = infer_confidence("peer", "user", "global", false, now(), None);
        assert_eq!(c, Confidence::Uncertain);
    }

    #[test]
    fn hypothetical_is_uncertain() {
        let c = infer_confidence("executive", "policy", "hypothetical", false, now(), None);
        assert_eq!(c, Confidence::Uncertain);
    }

    #[test]
    fn expired_user_fact_is_stale() {
        let old = Utc::now() - Duration::days(100); // > 90 day TTL for user
        let c = infer_confidence("user", "user", "global", false, old, Some(now()));
        assert_eq!(c, Confidence::Stale);
    }

    #[test]
    fn fresh_user_fact_not_stale() {
        let recent = Utc::now() - Duration::days(30);
        let c = infer_confidence("user", "user", "global", false, recent, Some(now()));
        assert_ne!(c, Confidence::Stale);
    }

    #[test]
    fn policy_has_long_ttl() {
        let old = Utc::now() - Duration::days(300); // < 365 day TTL for policy
        let c = infer_confidence("policy", "policy", "global", false, old, Some(now()));
        assert_eq!(c, Confidence::Definite); // not stale yet
    }

    #[test]
    fn annotations() {
        assert_eq!(Confidence::Definite.annotation(), " [CONFIRMED]");
        assert_eq!(Confidence::Likely.annotation(), "");
        assert_eq!(Confidence::Stale.annotation(), " [STALE]");
    }
}