converge_core/traits/mod.rs
1// Copyright 2024-2025 Aprio One AB, Sweden
2// Author: Kenneth Pernyer, kenneth@aprio.one
3// SPDX-License-Identifier: LicenseRef-Proprietary
4// All rights reserved. This source code is proprietary and confidential.
5// Unauthorized copying, modification, or distribution is strictly prohibited.
6
7//! # Capability Boundary Traits
8//!
9//! This module defines the abstraction layer for external capabilities that
10//! `converge-core` depends on but does not implement. These traits define
11//! the interface contract — implementations belong in capability crates like
12//! `converge-runtime`.
13//!
14//! ## Design Philosophy
15//!
16//! - **converge-core defines interfaces only**: No implementation of these
17//! traits exists in this crate. This keeps the core dependency-free and
18//! focused on correctness axioms.
19//!
20//! - **Capability crates provide implementations**: `converge-runtime` or
21//! similar crates implement these traits using appropriate libraries
22//! (e.g., rayon for Executor, rand for Randomness, sha2/hex for Fingerprint).
23//!
24//! - **Thread safety required**: All traits require `Send + Sync` bounds
25//! to ensure safe use in concurrent contexts.
26//!
27//! ## Traits
28//!
29//! ### Execution & Infrastructure
30//! - [`Executor`]: Abstracts parallel/sequential execution strategy
31//! - [`Randomness`]: Abstracts random number generation
32//! - [`Fingerprint`]: Abstracts cryptographic hashing and hex encoding
33//!
34//! ### Error Infrastructure
35//! - [`CapabilityError`]: Shared error classification interface
36//! - [`ErrorCategory`]: Error category enumeration
37//!
38//! ### LLM Capabilities
39//! - [`ChatBackend`]: Chat completion (GAT async pattern)
40//! - [`EmbedBackend`]: Embedding generation (GAT async pattern)
41//! - [`LlmBackend`]: Umbrella trait combining chat + embed
42//! - [`DynChatBackend`]: Dyn-safe chat wrapper for runtime polymorphism
43//! - [`DynEmbedBackend`]: Dyn-safe embed wrapper for runtime polymorphism
44//! - [`LlmError`]: LLM operation errors implementing [`CapabilityError`]
45//!
46//! ### Recall (Semantic Memory) Capabilities
47//! - [`RecallReader`]: Query-only read access (validation, audit, replay)
48//! - [`RecallWriter`]: Store/delete mutation access (ingestion pipelines)
49//! - [`Recall`]: Umbrella trait combining RecallReader + RecallWriter
50//! - [`DynRecallReader`]: Dyn-safe recall reader for runtime polymorphism
51//! - [`RecallError`]: Recall errors implementing [`CapabilityError`]
52//!
53//! ### ExperienceStore (Event Sourcing) Capabilities
54//! - [`ExperienceAppender`]: Append-only event storage (governance boundary)
55//! - [`ExperienceReplayer`]: Streaming replay access (audit, debugging)
56//! - [`DynExperienceAppender`]: Dyn-safe appender for runtime polymorphism
57//! - [`DynExperienceReplayer`]: Dyn-safe replayer for runtime polymorphism
58//! - [`StoreError`]: Store errors implementing [`CapabilityError`]
59//!
60//! ### Validation Capabilities (Type-State Aware)
61//! - [`Validator`]: Validates `Proposal<Draft>` producing `ValidationReport`
62//! - [`DynValidator`]: Dyn-safe validator for runtime polymorphism
63//! - [`ValidatorError`]: Validation errors implementing [`CapabilityError`]
64//!
65//! ### Promotion Capabilities (Type-State Aware)
66//! - [`Promoter`]: Promotes `Proposal<Validated>` to `Fact`
67//! - [`DynPromoter`]: Dyn-safe promoter for runtime polymorphism
68//! - [`PromoterError`]: Promotion errors implementing [`CapabilityError`]
69//! - [`PromotionContext`]: Context for promotion operations
70//!
71//! # Design Tenets Alignment
72//!
73//! This module directly supports these tenets from [`crate`]:
74//!
75//! | Tenet | How This Module Supports It |
76//! |-------|----------------------------|
77//! | **Agents Suggest, Engine Decides** | [`Validator`] and [`Promoter`] are the decision boundary |
78//! | **No Hidden Work** | All trait operations are explicit, no background effects |
79//! | **Purity Declaration** | Traits only; implementations belong in capability crates |
80//! | **Transparent Determinism** | [`Randomness`] abstracted for deterministic testing |
81//!
82//! # Cross-Module References
83//!
84//! - **Types**: [`Validator`] validates [`crate::types::Proposal`], [`Promoter`] creates [`crate::types::Fact`]
85//! - **Gates**: [`crate::gates::PromotionGate`] uses these traits for validation/promotion lifecycle
86
87// ============================================================================
88// Submodules
89// ============================================================================
90
91mod error;
92mod llm;
93mod promoter;
94mod recall;
95mod store;
96mod validator;
97
98// ============================================================================
99// Re-exports
100// ============================================================================
101
102// Error infrastructure
103pub use error::{CapabilityError, ErrorCategory};
104
105// LLM capability traits
106pub use llm::{
107 BoxFuture, ChatBackend, ChatMessage, ChatRequest, ChatResponse, ChatRole, DynChatBackend,
108 DynEmbedBackend, EmbedBackend, EmbedRequest, EmbedResponse, FinishReason, LlmBackend,
109 LlmError, TokenUsage,
110};
111
112// Recall (semantic memory) capability traits
113pub use recall::{
114 DynRecallReader, Recall, RecallError, RecallReader, RecallRecord, RecallRecordMetadata,
115 RecallWriter,
116};
117
118// ExperienceStore (event sourcing) capability traits
119pub use store::{
120 DynExperienceAppender, DynExperienceReplayer, ExperienceAppender, ExperienceReplayer,
121 ReplayBatch, ReplayCursor, ReplayOptions, StoreError,
122};
123
124// Validation capability traits
125pub use validator::{DynValidator, Validator, ValidatorError};
126
127// Promotion capability traits
128pub use promoter::{DynPromoter, Promoter, PromoterError, PromotionContext};
129
130// ============================================================================
131// Inline Traits (Infrastructure)
132// ============================================================================
133
134use std::fmt;
135
136/// Error type for fingerprint operations.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum FingerprintError {
139 /// The hex string contains invalid characters.
140 InvalidHex(String),
141 /// The hex string has incorrect length (expected 64 characters for 32 bytes).
142 InvalidLength { expected: usize, got: usize },
143}
144
145impl fmt::Display for FingerprintError {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self {
148 FingerprintError::InvalidHex(msg) => write!(f, "invalid hex: {}", msg),
149 FingerprintError::InvalidLength { expected, got } => {
150 write!(f, "invalid length: expected {} chars, got {}", expected, got)
151 }
152 }
153 }
154}
155
156impl std::error::Error for FingerprintError {}
157
158/// Abstracts parallel or sequential execution strategy.
159///
160/// This trait replaces direct usage of rayon's parallel iterators. By abstracting
161/// the execution strategy, `converge-core` can remain dependency-free while
162/// allowing runtime crates to provide optimized parallel implementations.
163///
164/// # Thread Safety
165///
166/// Implementations must be `Send + Sync` to allow sharing across threads.
167///
168/// # Example Implementation
169///
170/// ```ignore
171/// // In converge-runtime:
172/// use rayon::prelude::*;
173///
174/// pub struct RayonExecutor;
175///
176/// impl Executor for RayonExecutor {
177/// fn execute_parallel<T, F, R>(&self, items: &[T], f: F) -> Vec<R>
178/// where
179/// T: Sync,
180/// F: Fn(&T) -> R + Send + Sync,
181/// R: Send,
182/// {
183/// items.par_iter().map(f).collect()
184/// }
185/// }
186/// ```
187pub trait Executor: Send + Sync {
188 /// Execute a function over a slice of items, potentially in parallel.
189 ///
190 /// The implementation may choose to execute sequentially or in parallel
191 /// based on the item count, available cores, or other heuristics.
192 ///
193 /// # Arguments
194 ///
195 /// * `items` - The slice of items to process
196 /// * `f` - The function to apply to each item
197 ///
198 /// # Returns
199 ///
200 /// A vector of results in the same order as the input items.
201 fn execute_parallel<T, F, R>(&self, items: &[T], f: F) -> Vec<R>
202 where
203 T: Sync,
204 F: Fn(&T) -> R + Send + Sync,
205 R: Send;
206}
207
208/// Abstracts random number generation.
209///
210/// This trait replaces direct usage of the `rand` crate. By abstracting
211/// randomness, `converge-core` can remain dependency-free and tests can
212/// use deterministic implementations for reproducibility.
213///
214/// # Thread Safety
215///
216/// Implementations must be `Send + Sync` to allow sharing across threads.
217///
218/// # Example Implementation
219///
220/// ```ignore
221/// // In converge-runtime:
222/// use rand::Rng;
223/// use std::sync::Mutex;
224///
225/// pub struct ThreadRng {
226/// rng: Mutex<rand::rngs::ThreadRng>,
227/// }
228///
229/// impl Randomness for ThreadRng {
230/// fn random_u32(&self) -> u32 {
231/// self.rng.lock().unwrap().gen()
232/// }
233///
234/// fn random_bytes(&self, buf: &mut [u8]) {
235/// self.rng.lock().unwrap().fill(buf);
236/// }
237/// }
238/// ```
239pub trait Randomness: Send + Sync {
240 /// Generate a random 32-bit unsigned integer.
241 fn random_u32(&self) -> u32;
242
243 /// Fill a buffer with random bytes.
244 fn random_bytes(&self, buf: &mut [u8]);
245}
246
247/// Abstracts cryptographic fingerprinting (hashing) and hex encoding.
248///
249/// This trait replaces direct usage of `sha2` and `hex` crates. By abstracting
250/// fingerprinting, `converge-core` can remain dependency-free and tests can
251/// use mock implementations when needed.
252///
253/// # Thread Safety
254///
255/// Implementations must be `Send + Sync` to allow sharing across threads.
256///
257/// # Hash Size
258///
259/// This trait uses 32-byte (256-bit) hashes, compatible with SHA-256.
260///
261/// # Example Implementation
262///
263/// ```ignore
264/// // In converge-runtime:
265/// use sha2::{Sha256, Digest};
266///
267/// pub struct Sha256Fingerprint;
268///
269/// impl Fingerprint for Sha256Fingerprint {
270/// fn compute(&self, data: &[u8]) -> [u8; 32] {
271/// let mut hasher = Sha256::new();
272/// hasher.update(data);
273/// hasher.finalize().into()
274/// }
275///
276/// fn to_hex(hash: &[u8; 32]) -> String {
277/// hex::encode(hash)
278/// }
279///
280/// fn from_hex(s: &str) -> Result<[u8; 32], FingerprintError> {
281/// let bytes = hex::decode(s)
282/// .map_err(|e| FingerprintError::InvalidHex(e.to_string()))?;
283/// bytes.try_into()
284/// .map_err(|_| FingerprintError::InvalidLength {
285/// expected: 32,
286/// got: bytes.len(),
287/// })
288/// }
289/// }
290/// ```
291pub trait Fingerprint: Send + Sync {
292 /// Compute a 32-byte cryptographic fingerprint of the input data.
293 fn compute(&self, data: &[u8]) -> [u8; 32];
294
295 /// Convert a 32-byte hash to a lowercase hexadecimal string.
296 fn to_hex(hash: &[u8; 32]) -> String;
297
298 /// Parse a hexadecimal string into a 32-byte hash.
299 ///
300 /// # Errors
301 ///
302 /// Returns `FingerprintError::InvalidHex` if the string contains non-hex characters.
303 /// Returns `FingerprintError::InvalidLength` if the string is not exactly 64 characters.
304 fn from_hex(s: &str) -> Result<[u8; 32], FingerprintError>;
305}