Skip to main content

ainl_context_freshness/
lib.rs

1//! Pure functions for [`ContextFreshness`](ainl_contracts::ContextFreshness) and execution gating.
2//!
3//! See also: [`ainl-context-compiler`](https://docs.rs/ainl-context-compiler) for LLM
4//! context-window assembly (different lifecycle phase — this crate gates *tool execution* based
5//! on repo-knowledge currency; the compiler crate assembles *prompt bytes* sent to the LLM).
6//! Both crates can be used independently or together.
7
8use ainl_contracts::{ContextFreshness, ImpactDecision};
9
10/// Inputs for freshness evaluation (extend without breaking callers).
11#[derive(Debug, Clone, Default)]
12pub struct FreshnessInputs {
13    /// When true, index/repo graph is known stale vs HEAD.
14    pub index_stale_vs_head: Option<bool>,
15    /// When true, freshness could not be determined.
16    pub unknown: bool,
17}
18
19/// Decide freshness from explicit inputs.
20#[must_use]
21pub fn evaluate_freshness(i: &FreshnessInputs) -> ContextFreshness {
22    if i.unknown {
23        return ContextFreshness::Unknown;
24    }
25    match i.index_stale_vs_head {
26        Some(true) => ContextFreshness::Stale,
27        Some(false) => ContextFreshness::Fresh,
28        None => ContextFreshness::Unknown,
29    }
30}
31
32/// Policy: when to block execution until context is refreshed.
33#[must_use]
34pub fn impact_decision_strict(f: ContextFreshness, repo_intel_ready: bool) -> ImpactDecision {
35    match f {
36        ContextFreshness::Stale => {
37            if repo_intel_ready {
38                ImpactDecision::RequireImpactFirst
39            } else {
40                ImpactDecision::BlockUntilFresh
41            }
42        }
43        ContextFreshness::Unknown => {
44            // Strict policy: even without repo intel we still require impact-first
45            // checks for unknown freshness (avoids silent execution on stale state).
46            let _ = repo_intel_ready;
47            ImpactDecision::RequireImpactFirst
48        }
49        ContextFreshness::Fresh => ImpactDecision::AllowExecute,
50    }
51}
52
53/// Lenient policy: never block, only suggest impact when stale/unknown if repo intel exists.
54#[must_use]
55pub fn impact_decision_balanced(f: ContextFreshness, repo_intel_ready: bool) -> ImpactDecision {
56    match f {
57        ContextFreshness::Fresh => ImpactDecision::AllowExecute,
58        ContextFreshness::Stale | ContextFreshness::Unknown => {
59            if repo_intel_ready {
60                ImpactDecision::RequireImpactFirst
61            } else {
62                ImpactDecision::AllowExecute
63            }
64        }
65    }
66}
67
68/// Combine with compile/run gate for AINL MCP.
69#[must_use]
70pub fn can_execute_with_context(f: ContextFreshness, strict: bool, repo_intel_ready: bool) -> bool {
71    let d = if strict {
72        impact_decision_strict(f, repo_intel_ready)
73    } else {
74        impact_decision_balanced(f, repo_intel_ready)
75    };
76    matches!(d, ImpactDecision::AllowExecute)
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn fresh_allows() {
85        assert!(can_execute_with_context(
86            ContextFreshness::Fresh,
87            true,
88            false
89        ));
90    }
91}