Skip to main content

lean_ctx_ocla/
traits.rs

1// SPDX-License-Identifier: Apache-2.0
2use async_trait::async_trait;
3use lean_ctx_protocol::CapabilityManifestV1;
4
5use crate::types::{
6    AgentEnvelope, AgentMessageRequest, CompressionRequest, CompressionResult, ConfigProposal,
7    ConfigTuningRequest, ConnectorJob, DeliveryEntry, DeliveryRecord, DeliveryRecordResult,
8    DeliveryStats, EfficiencyAnalysis, EfficiencySample, ExperimentRequest, ExperimentResult,
9    IntentDecision, IntentRequest, MetricPoint, Observation, OclaCapability, OclaResult, Outcome,
10    ResponseOptimizationRequest, ResponseOptimizationResult, SavingsEvidence, ScheduledJob,
11    UsageRecord,
12};
13
14/// Common, versioned discovery surface for every OCLA capability.
15pub trait OclaService: Send + Sync {
16    fn capability(&self) -> OclaCapability;
17
18    /// Returns an explicit versioned contract; missing declarations are unavailable.
19    fn manifest(&self) -> OclaResult<CapabilityManifestV1> {
20        Err(crate::types::OclaError::Unavailable(self.capability().kind))
21    }
22}
23
24#[async_trait]
25pub trait ObservationHook: OclaService {
26    async fn observe(&self, observation: Observation) -> OclaResult<()>;
27}
28
29#[async_trait]
30pub trait UsageSink: OclaService {
31    async fn record_usage(&self, usage: UsageRecord) -> OclaResult<()>;
32}
33
34#[async_trait]
35pub trait MetricsExporter: OclaService {
36    async fn export_metrics(&self, metrics: Vec<MetricPoint>) -> OclaResult<()>;
37}
38
39pub trait SavingsLedger: OclaService {
40    fn record_savings(&self, evidence: SavingsEvidence) -> OclaResult<String>;
41
42    /// Update capability-local summaries and evidence for a caller-owned
43    /// accounting observation, without appending another accounting event.
44    fn project_savings(&self, evidence: SavingsEvidence) -> OclaResult<String>;
45}
46
47pub trait IntentClassifier: OclaService {
48    fn classify_intent(&self, request: IntentRequest) -> OclaResult<IntentDecision>;
49}
50
51pub trait OutcomeTracker: OclaService {
52    fn record_outcome(&self, outcome: Outcome) -> OclaResult<()>;
53}
54
55pub trait CompressionProvider: OclaService {
56    fn compress(&self, request: CompressionRequest) -> OclaResult<CompressionResult>;
57}
58
59#[async_trait]
60pub trait ResponseOptimizer: OclaService {
61    async fn optimize_response(
62        &self,
63        request: ResponseOptimizationRequest,
64    ) -> OclaResult<ResponseOptimizationResult>;
65}
66
67pub trait EfficiencyAnalyzer: OclaService {
68    fn analyze_efficiency(&self, sample: EfficiencySample) -> OclaResult<EfficiencyAnalysis>;
69}
70
71pub trait ConfigTuner: OclaService {
72    fn propose_tuning(&self, request: ConfigTuningRequest) -> OclaResult<ConfigProposal>;
73}
74
75pub trait ExperimentRunner: OclaService {
76    fn run_experiment(&self, request: ExperimentRequest) -> OclaResult<ExperimentResult>;
77}
78
79pub trait ConnectorScheduler: OclaService {
80    fn schedule_connector(&self, job: ConnectorJob) -> OclaResult<ScheduledJob>;
81}
82
83pub trait AgentGateway: OclaService {
84    fn relay_agent(&self, envelope: AgentEnvelope) -> OclaResult<AgentEnvelope>;
85    fn route_message(&self, request: &AgentMessageRequest) -> OclaResult<String>;
86}
87
88pub trait DeliveryRegistry: OclaService {
89    /// Scope and requester identity must be established by the trusted caller.
90    fn check_scoped_delivery(
91        &self,
92        blake3: &[u8; 12],
93        path: &str,
94        scope: &crate::delivery_scope::DeliveryScopeV1,
95        requester_agent_id: &str,
96        requester_conversation_id: Option<&str>,
97    ) -> Option<DeliveryRecord>;
98
99    fn check_delivery(
100        &self,
101        blake3: &[u8; 12],
102        mtime: u64,
103        path: &str,
104        requester_agent_id: Option<&str>,
105        requester_conversation_id: Option<&str>,
106    ) -> Option<DeliveryRecord>;
107    fn record_stub_served(&self, record: &DeliveryRecord, stub_tokens: u64);
108    fn record_delivery(&self, entry: DeliveryEntry) -> DeliveryRecordResult;
109    fn delivery_stats(&self) -> DeliveryStats;
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::types::OclaCapabilityKind;
116
117    struct UndeclaredService {
118        kind: OclaCapabilityKind,
119    }
120
121    impl OclaService for UndeclaredService {
122        fn capability(&self) -> OclaCapability {
123            OclaCapability::available(self.kind)
124        }
125    }
126
127    #[test]
128    fn missing_manifest_is_unavailable_for_every_capability_kind() {
129        for kind in OclaCapabilityKind::ALL {
130            assert!(matches!(
131                UndeclaredService { kind }.manifest(),
132                Err(crate::types::OclaError::Unavailable(actual)) if actual == kind
133            ));
134        }
135    }
136
137    #[test]
138    fn every_public_trait_is_object_safe() {
139        fn assert_object_safe<T: ?Sized>() {}
140        assert_object_safe::<dyn ObservationHook>();
141        assert_object_safe::<dyn UsageSink>();
142        assert_object_safe::<dyn MetricsExporter>();
143        assert_object_safe::<dyn SavingsLedger>();
144        assert_object_safe::<dyn IntentClassifier>();
145        assert_object_safe::<dyn OutcomeTracker>();
146        assert_object_safe::<dyn CompressionProvider>();
147        assert_object_safe::<dyn ResponseOptimizer>();
148        assert_object_safe::<dyn EfficiencyAnalyzer>();
149        assert_object_safe::<dyn ConfigTuner>();
150        assert_object_safe::<dyn ExperimentRunner>();
151        assert_object_safe::<dyn ConnectorScheduler>();
152        assert_object_safe::<dyn AgentGateway>();
153        assert_object_safe::<dyn DeliveryRegistry>();
154    }
155}