Skip to main content

lean_ctx/core/providers/
provider_trait.rs

1use super::{ProviderItem, ProviderResult};
2
3/// Plugin-ready trait for external context providers.
4///
5/// A ContextProvider connects LeanCTX to external data sources (issue trackers,
6/// CI systems, documentation, etc.) and returns structured context that flows
7/// through the standard compression and IR pipeline.
8///
9/// The existing GitLab provider implements this interface pattern.
10/// Future plugins will register implementations dynamically via the provider
11/// framework contract.
12pub trait ContextProvider: Send + Sync {
13    /// Unique identifier for this provider (e.g. "gitlab", "github", "jira").
14    fn id(&self) -> &'static str;
15
16    /// Human-readable display name.
17    fn display_name(&self) -> &'static str;
18
19    /// Returns the actions this provider supports (e.g. "issues", "mrs", "pipelines").
20    fn supported_actions(&self) -> &[&str];
21
22    /// Execute a provider action and return structured results.
23    fn execute(&self, action: &str, params: &ProviderParams) -> Result<ProviderResult, String>;
24
25    /// Stable, credential-bound identity of a read-only resource. A name alone
26    /// does not authorize reuse; providers without this contract remain unbound.
27    fn reuse_binding(&self, _action: &str, _params: &ProviderParams) -> Option<String> {
28        None
29    }
30
31    /// Fetch current evidence for one previously observed item. Never implement
32    /// this with a cached result or a project-only permission probe.
33    fn reacquire_item(
34        &self,
35        _action: &str,
36        _params: &ProviderParams,
37        _item_id: &str,
38    ) -> Result<ProviderResult, String> {
39        Err("provider does not support current item authorization".into())
40    }
41
42    /// TTL for caching results from this provider (in seconds).
43    fn cache_ttl_secs(&self) -> u64 {
44        120
45    }
46
47    /// Whether a result of `action` is consolidated into the stores (BM25,
48    /// cross-source edges, knowledge). A provider whose actions include
49    /// reads of something one listing already indexes says `false` for
50    /// them, so a read does not add a second copy and its own hints.
51    fn consolidates(&self, _action: &str) -> bool {
52        true
53    }
54
55    /// What this connector actually guarantees (strategy package 2026-10-14,
56    /// doc 17). The default claims nothing it cannot prove: it lists, and it
57    /// cannot re-check a stored item, so its content stays personal-local and
58    /// is withheld from governed reuse.
59    fn descriptor(&self) -> ConnectorDescriptor {
60        ConnectorDescriptor::list_only("token or none")
61    }
62
63    /// Whether this provider requires authentication.
64    fn requires_auth(&self) -> bool {
65        true
66    }
67
68    /// Check if the provider is configured and ready to serve requests.
69    fn is_available(&self) -> bool;
70}
71
72/// How far a connector supports one capability. `Partial` always names its
73/// limit in the descriptor's `notes`.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
75#[serde(rename_all = "snake_case")]
76pub enum CapabilityLevel {
77    Supported,
78    Partial,
79    Unsupported,
80}
81
82/// The connector contract of doc 17: what a source connector can do, how it
83/// fails and what it may write. Declared by the connector, rendered into the
84/// capability matrix and consulted before governed or shared use.
85#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
86pub struct ConnectorDescriptor {
87    /// How the principal is bound (token, OAuth data source, none).
88    pub principal_binding: &'static str,
89    pub search: CapabilityLevel,
90    pub list: CapabilityLevel,
91    pub get: CapabilityLevel,
92    pub delta: CapabilityLevel,
93    /// Re-check, with the principal's own credential, that a stored item is
94    /// still readable (`reacquire_item`).
95    pub revalidate_acl: CapabilityLevel,
96    /// Detect that a stored item changed since it was read.
97    pub revalidate_revision: CapabilityLevel,
98    pub delete_event: CapabilityLevel,
99    /// Source text is data, never instructions, whatever its detectors say.
100    pub content_trust: &'static str,
101    /// Mutations of the source: none. Writes belong to the host application.
102    pub write_surface: &'static str,
103    /// Error, timeout, rate limit or lost access: what the gateway does.
104    pub failure_policy: &'static str,
105    pub notes: &'static str,
106}
107
108impl ConnectorDescriptor {
109    pub(crate) const UNTRUSTED: &'static str = "untrusted data";
110    pub(crate) const NO_WRITES: &'static str = "none";
111    pub(crate) const FAIL_CLOSED: &'static str = "deny; no stale or cached answer";
112
113    pub(crate) fn list_only(principal_binding: &'static str) -> Self {
114        Self {
115            principal_binding,
116            search: CapabilityLevel::Unsupported,
117            list: CapabilityLevel::Supported,
118            get: CapabilityLevel::Unsupported,
119            delta: CapabilityLevel::Unsupported,
120            revalidate_acl: CapabilityLevel::Unsupported,
121            revalidate_revision: CapabilityLevel::Unsupported,
122            delete_event: CapabilityLevel::Unsupported,
123            content_trust: Self::UNTRUSTED,
124            write_surface: Self::NO_WRITES,
125            failure_policy: Self::FAIL_CLOSED,
126            notes: "no item re-check: personal-local use only, withheld from governed reuse",
127        }
128    }
129
130    /// Governed or shared reuse of a stored item needs a current ACL check.
131    pub fn allows_shared_reuse(&self) -> bool {
132        self.revalidate_acl == CapabilityLevel::Supported
133    }
134}
135
136/// Parameters passed to a provider action.
137#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
138pub struct ProviderParams {
139    pub project: Option<String>,
140    pub state: Option<String>,
141    pub limit: Option<usize>,
142    pub query: Option<String>,
143    pub id: Option<String>,
144}
145
146/// A context packet produced by a provider, ready for the IR pipeline.
147#[derive(Debug, Clone)]
148pub struct ContextPacket {
149    pub provider_id: String,
150    pub action: String,
151    pub items: Vec<ProviderItem>,
152    pub token_count_raw: usize,
153    pub token_count_compressed: usize,
154    pub cache_hit: bool,
155}