convergio_types/extension.rs
1//! The Extension trait — the only way to exist in Convergio.
2//!
3//! Every module (core or plugin) implements this trait.
4//! No alternative, no workaround. If you don't implement Extension, you don't exist.
5
6use crate::events::{DomainEvent, EventFilter};
7use crate::manifest::Manifest;
8
9/// Result type used across all extensions.
10pub type ExtResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
11
12/// A database migration owned by a module.
13pub struct Migration {
14 /// Monotonically increasing version number for this module.
15 pub version: u32,
16 /// Human-readable description.
17 pub description: &'static str,
18 /// SQL to apply (forward only for now).
19 pub up: &'static str,
20}
21
22/// Health status reported by each extension.
23#[derive(Debug, Clone, serde::Serialize)]
24pub enum Health {
25 Ok,
26 Degraded { reason: String },
27 Down { reason: String },
28}
29
30/// A named metric emitted by an extension.
31#[derive(Debug, Clone, serde::Serialize)]
32pub struct Metric {
33 pub name: String,
34 pub value: f64,
35 pub labels: Vec<(String, String)>,
36}
37
38/// A scheduled task declared by an extension.
39pub struct ScheduledTask {
40 pub name: &'static str,
41 /// Cron expression (e.g. "0 2 * * *" for 2 AM daily).
42 pub cron: &'static str,
43}
44
45/// An MCP tool definition declared by an extension.
46///
47/// The MCP server discovers these at runtime via `/api/meta/mcp-tools`.
48/// When an extension adds routes, it also declares the corresponding
49/// MCP tools here — no manual registry maintenance needed.
50#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
51pub struct McpToolDef {
52 pub name: String,
53 pub description: String,
54 /// HTTP method: "GET", "POST", "PUT", "DELETE"
55 pub method: String,
56 /// URL path with `:param` placeholders (e.g. "/api/plan-db/json/:plan_id")
57 pub path: String,
58 /// JSON Schema for the tool's input parameters.
59 pub input_schema: serde_json::Value,
60 /// Minimum ring required: "sandboxed", "community", "trusted", "core"
61 pub min_ring: String,
62 /// Path parameter names to interpolate from input args.
63 #[serde(default)]
64 pub path_params: Vec<String>,
65}
66
67/// The one trait to rule them all.
68///
69/// Implement this to register your module with the Convergio daemon.
70/// The daemon calls these methods at startup and runtime.
71pub trait Extension: Send + Sync {
72 /// Identity and capabilities — who you are, what you do.
73 fn manifest(&self) -> Manifest;
74
75 /// Your database tables. The migration runner applies these at startup,
76 /// tracked in `_schema_registry`.
77 fn migrations(&self) -> Vec<Migration> {
78 vec![]
79 }
80
81 /// Your HTTP routes. The server mounts these under the appropriate prefix.
82 fn routes(&self, ctx: &AppContext) -> Option<axum::Router> {
83 let _ = ctx;
84 None
85 }
86
87 /// Called once after migrations and before routes are served.
88 fn on_start(&self, ctx: &AppContext) -> ExtResult<()> {
89 let _ = ctx;
90 Ok(())
91 }
92
93 /// Called on graceful shutdown.
94 fn on_shutdown(&self) -> ExtResult<()> {
95 Ok(())
96 }
97
98 /// Health check — aggregated into `/health/deep`.
99 fn health(&self) -> Health {
100 Health::Ok
101 }
102
103 /// Metrics — collected by the telemetry system.
104 fn metrics(&self) -> Vec<Metric> {
105 vec![]
106 }
107
108 /// Domain events this extension subscribes to.
109 fn subscriptions(&self) -> Vec<EventFilter> {
110 vec![]
111 }
112
113 /// Called when a subscribed domain event fires.
114 fn on_event(&self, event: &DomainEvent) {
115 let _ = event;
116 }
117
118 /// Periodic tasks (cron-like).
119 fn scheduled_tasks(&self) -> Vec<ScheduledTask> {
120 vec![]
121 }
122
123 /// Called when a scheduled task fires (cron match).
124 fn on_scheduled_task(&self, task_name: &str) {
125 let _ = task_name;
126 }
127
128 /// Called when configuration changes at runtime.
129 fn on_config_change(&self, key: &str, value: &serde_json::Value) -> ExtResult<()> {
130 let _ = (key, value);
131 Ok(())
132 }
133
134 /// MCP tool definitions exposed by this extension.
135 ///
136 /// The daemon aggregates these into `/api/meta/mcp-tools` and the MCP
137 /// server discovers them at startup. Add entries here whenever you add
138 /// HTTP routes that agents or Jarvis should be able to call.
139 fn mcp_tools(&self) -> Vec<McpToolDef> {
140 vec![]
141 }
142}
143
144/// Shared application context passed to extensions.
145///
146/// Type-erased resource map: the server fills it with concrete types
147/// (ConnPool, config, health registry, …) and extensions retrieve
148/// them by type via `get::<T>()`.
149#[derive(Default)]
150pub struct AppContext {
151 resources: std::collections::HashMap<
152 std::any::TypeId,
153 std::sync::Arc<dyn std::any::Any + Send + Sync>,
154 >,
155}
156
157impl AppContext {
158 pub fn new() -> Self {
159 Self::default()
160 }
161
162 /// Insert a resource. Overwrites any previous value of the same type.
163 pub fn insert<T: 'static + Send + Sync>(&mut self, val: T) {
164 self.resources
165 .insert(std::any::TypeId::of::<T>(), std::sync::Arc::new(val));
166 }
167
168 /// Retrieve a shared reference to a resource by type.
169 pub fn get<T: 'static + Send + Sync>(&self) -> Option<&T> {
170 self.resources
171 .get(&std::any::TypeId::of::<T>())
172 .and_then(|v| v.downcast_ref())
173 }
174
175 /// Retrieve an `Arc<T>` clone for a resource.
176 pub fn get_arc<T: 'static + Send + Sync>(&self) -> Option<std::sync::Arc<T>> {
177 self.resources
178 .get(&std::any::TypeId::of::<T>())
179 .and_then(|v| v.clone().downcast().ok())
180 }
181}