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}