747 lines
22 KiB
Markdown
747 lines
22 KiB
Markdown
# daemon-cli Architecture
|
|
|
|
**daemon-cli** is the orchestration hub of the prov-ecosystem, providing unified daemon and CLI interfaces for all ecosystem services.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture Overview](#architecture-overview)
|
|
2. [Stable API Contracts](#stable-api-contracts)
|
|
3. [Module Organization](#module-organization)
|
|
4. [Core Concepts](#core-concepts)
|
|
5. [Extension Points](#extension-points)
|
|
6. [Best Practices](#best-practices)
|
|
7. [Integration Examples](#integration-examples)
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### Design Pattern: Plugin + Hub Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ daemon-cli │
|
|
│ (Orchestration Hub) │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
│ │ Core Infrastructure │ │
|
|
│ │ ├─ DaemonConfig (Configuration) │ │
|
|
│ │ ├─ EventBus (Pub-Sub) │ │
|
|
│ │ └─ OperationRegistry (Plugin Discovery) │ │
|
|
│ └──────────────────────────────────────────────────┘ │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
│ │ Execution Layers │ │
|
|
│ │ ├─ HTTP API (provd daemon) │ │
|
|
│ │ ├─ CLI Client (provctl) │ │
|
|
│ │ └─ Dual-mode Execution (daemon or direct) │ │
|
|
│ └──────────────────────────────────────────────────┘ │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
│ │ Observability & Integration │ │
|
|
│ │ ├─ Health Probes (Kubernetes-ready) │ │
|
|
│ │ ├─ Webhooks (Event Ingestion) │ │
|
|
│ │ ├─ Hierarchical Caching (3-layer LRU) │ │
|
|
│ │ ├─ Internationalization (i18n) │ │
|
|
│ │ └─ Config Rendering (KCL/Nickel/Tera) │ │
|
|
│ └──────────────────────────────────────────────────┘ │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
│ │ Ecosystem Operations (Plugins) │ │
|
|
│ │ ├─ valida (config validation) │ │
|
|
│ │ ├─ encrypt (encryption) │ │
|
|
│ │ ├─ runtime (container runtime detection) │ │
|
|
│ │ ├─ init-servs (service management) │ │
|
|
│ │ ├─ observability (monitoring) │ │
|
|
│ │ └─ audit (security scanning) │ │
|
|
│ └──────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Execution Flow
|
|
|
|
```
|
|
Client Request
|
|
↓
|
|
┌─ provctl (CLI) or HTTP API endpoint
|
|
├─ Parse input and create Operation request
|
|
├─ EventBus publishes "operation_requested" event
|
|
├─ OperationRegistry selects matching EcosystemOperation
|
|
├─ Check HierarchicalCache for cached result
|
|
├─ If miss: Execute operation (e.g., valida::ValidaOperation::execute)
|
|
├─ Cache result with TTL
|
|
├─ EventBus publishes "operation_completed" event
|
|
└─ Return result to client
|
|
```
|
|
|
|
---
|
|
|
|
## Stable API Contracts
|
|
|
|
### Why Contracts Matter
|
|
|
|
The daemon-cli module is heavily depended on by other crates (52+ dependents). To maintain compatibility:
|
|
|
|
- **Stable APIs** are guaranteed to remain compatible across minor version updates
|
|
- **Internal modules** may change between versions without notice
|
|
- **Contracts module** explicitly defines stability boundaries
|
|
|
|
### Accessing Stable APIs
|
|
|
|
**Always prefer importing from the `contracts` module:**
|
|
|
|
```rust
|
|
// ✅ GOOD - Stable contract
|
|
use daemon_cli::contracts::stable::{EcosystemOperation, OperationRegistry};
|
|
use daemon_cli::contracts::observability::HealthProbe;
|
|
|
|
// ⚠️ OK but less clear - Direct import (same types, but unstable guarantee)
|
|
use daemon_cli::{EcosystemOperation, HealthProbe};
|
|
|
|
// ❌ AVOID - Internal implementation details
|
|
use daemon_cli::cache::HierarchicalCache; // Not in contracts
|
|
use daemon_cli::rendering::TemplateEngine; // Not in contracts
|
|
```
|
|
|
|
### Stable API Categories
|
|
|
|
#### 1. **Core/Stable** - Orchestration Contract
|
|
```rust
|
|
pub mod contracts::stable {
|
|
pub use crate::core::{DaemonConfig, DaemonError, Result};
|
|
pub use crate::orchestration::{EcosystemOperation, OperationRegistry};
|
|
pub use crate::api::AppState;
|
|
pub use crate::events::{Event, EventBus, EventType, EventPayload};
|
|
}
|
|
```
|
|
|
|
**Use When:**
|
|
- Implementing new operations (extend `EcosystemOperation`)
|
|
- Registering with `OperationRegistry`
|
|
- Publishing events to `EventBus`
|
|
- Accessing shared `AppState`
|
|
|
|
#### 2. **Execution/Stable** - CLI and Output
|
|
```rust
|
|
pub mod contracts::execution {
|
|
pub use crate::cli::{DaemonClient, OfflineMode, OutputFormat};
|
|
pub use crate::config_renderer::{ConfigRenderer, RenderRequest};
|
|
}
|
|
```
|
|
|
|
**Use When:**
|
|
- Running in CLI mode (use `DaemonClient` or `OfflineMode`)
|
|
- Rendering configuration (KCL, Nickel, Tera templates)
|
|
- Formatting output (JSON, YAML, Table, etc.)
|
|
|
|
#### 3. **Observability/Stable** - Monitoring
|
|
```rust
|
|
pub mod contracts::observability {
|
|
pub use crate::health::{HealthMetrics, HealthProbe, ProbeStatus};
|
|
}
|
|
```
|
|
|
|
**Use When:**
|
|
- Implementing health check endpoints
|
|
- Collecting metrics
|
|
- Exposing readiness/liveness probes for Kubernetes
|
|
|
|
#### 4. **I18n/Stable** - Internationalization
|
|
```rust
|
|
pub mod contracts::i18n {
|
|
pub use crate::i18n::{
|
|
I18nManager, TranslationRequest, TranslationResponse,
|
|
TranslationContext,
|
|
};
|
|
}
|
|
```
|
|
|
|
**Use When:**
|
|
- Supporting multiple languages in messages
|
|
- Translating output
|
|
- Managing locale-aware formatting
|
|
|
|
#### 5. **Integration/Stable** - External Systems
|
|
```rust
|
|
pub mod contracts::integration {
|
|
pub use crate::webhooks::{WebHookEvent, WebHookHandler, WebHookStore};
|
|
}
|
|
```
|
|
|
|
**Use When:**
|
|
- Ingesting webhook events
|
|
- Storing webhook configurations
|
|
- Integrating with external CI/CD systems
|
|
|
|
---
|
|
|
|
## Module Organization
|
|
|
|
### Tier 1: Core Infrastructure (Stable)
|
|
| Module | Purpose | Stability |
|
|
|--------|---------|-----------|
|
|
| `core` | Configuration, errors, types | **STABLE** |
|
|
| `orchestration` | Operation trait, registry | **STABLE** |
|
|
| `events` | Event bus, pub-sub | **STABLE** |
|
|
|
|
**What to know:**
|
|
- `DaemonConfig` loads from YAML/TOML/environment
|
|
- `EcosystemOperation` is the extension point for ecosystem crates
|
|
- `EventBus` uses async-broadcast for real-time events
|
|
|
|
### Tier 2: Execution Layers (Stable via Contracts)
|
|
| Module | Purpose | Stability |
|
|
|--------|---------|-----------|
|
|
| `api` | Axum HTTP server | **STABLE** (via contracts) |
|
|
| `cli` | DaemonClient + OfflineMode | **STABLE** (via contracts) |
|
|
| `config_renderer` | Multi-format rendering | **STABLE** (via contracts) |
|
|
|
|
**What to know:**
|
|
- `DaemonClient` connects to HTTP daemon over network
|
|
- `OfflineMode` executes operations directly (no daemon)
|
|
- Choose mode based on deployment: cloud (daemon) vs embedded (offline)
|
|
|
|
### Tier 3: Observability & Integration (Stable via Contracts)
|
|
| Module | Purpose | Stability |
|
|
|--------|---------|-----------|
|
|
| `health` | K8s probes, metrics | **STABLE** (via contracts) |
|
|
| `webhooks` | Event ingestion | **STABLE** (via contracts) |
|
|
| `i18n` | Internationalization | **STABLE** (via contracts) |
|
|
|
|
**What to know:**
|
|
- `HealthProbe` exposes readiness/liveness for Kubernetes
|
|
- `WebHookStore` persists webhook configurations
|
|
- `I18nManager` handles message translation
|
|
|
|
### Tier 4: Internal Implementation (Implementation Details - May Change)
|
|
| Module | Purpose | Note |
|
|
|--------|---------|------|
|
|
| `cache` | 3-layer LRU caching | Internal, use `contracts` instead |
|
|
| `rendering` | Template engine | Internal, use `config_renderer` |
|
|
| `nushell` | Shell integration | Feature-gated internal |
|
|
|
|
**Important:** These modules are NOT in `contracts` because they are implementation details and subject to change.
|
|
|
|
---
|
|
|
|
## Core Concepts
|
|
|
|
### 1. EcosystemOperation - The Extension Point
|
|
|
|
The `EcosystemOperation` trait is how ecosystem crates (valida, encrypt, runtime, etc.) integrate:
|
|
|
|
```rust
|
|
#[async_trait]
|
|
pub trait EcosystemOperation: Send + Sync {
|
|
fn name(&self) -> &'static str;
|
|
|
|
async fn execute(&self, params: serde_json::Value)
|
|
-> Result<OperationResult, OperationError>;
|
|
|
|
fn cache_key(&self, params: &serde_json::Value) -> String;
|
|
}
|
|
```
|
|
|
|
**Pattern: Self-Registration**
|
|
```rust
|
|
// In valida crate:
|
|
pub struct ValidaOperation;
|
|
|
|
#[async_trait]
|
|
impl EcosystemOperation for ValidaOperation {
|
|
fn name(&self) -> &'static str { "valida" }
|
|
|
|
async fn execute(&self, params: serde_json::Value)
|
|
-> Result<OperationResult, OperationError> {
|
|
let config_path = params["config_path"].as_str()?;
|
|
let rules = load_rules(config_path)?;
|
|
validate_config(&rules)?;
|
|
Ok(OperationResult { status: "success", data: json!({}) })
|
|
}
|
|
|
|
fn cache_key(&self, params: &serde_json::Value) -> String {
|
|
format!("valida:{}", params["config_path"])
|
|
}
|
|
}
|
|
|
|
// Registration happens via OperationRegistry::register()
|
|
registry.register(Arc::new(ValidaOperation));
|
|
```
|
|
|
|
### 2. Hierarchical Caching - Three Levels
|
|
|
|
**Not exposed in stable API (implementation detail)**, but important to understand:
|
|
|
|
```
|
|
Level 1: Command Cache (1 hour TTL)
|
|
└─ Full operation results cached by cache_key
|
|
└─ E.g., "valida:path/to/config.yaml" → validation result
|
|
|
|
Level 2: Config Cache (5 minutes TTL)
|
|
└─ Parsed configuration objects
|
|
└─ Shared across operations
|
|
|
|
Level 3: Module Cache (Permanent)
|
|
└─ Loaded modules, plugins, binaries
|
|
└─ Only evicted on explicit clear()
|
|
```
|
|
|
|
**Performance Impact:** Operations execute in <100ms when cached.
|
|
|
|
### 3. Event Bus - Pub-Sub Architecture
|
|
|
|
**Stable API:**
|
|
```rust
|
|
pub use daemon_cli::contracts::stable::EventBus;
|
|
|
|
// Publishing events
|
|
bus.publish(Event {
|
|
event_type: EventType::OperationStarted,
|
|
payload: EventPayload::Operation { name: "valida".into(), params: json!({}) },
|
|
});
|
|
|
|
// Subscribing to events
|
|
let mut subscriber = bus.subscribe();
|
|
while let Some(event) = subscriber.recv().await {
|
|
println!("Event: {:?}", event);
|
|
}
|
|
```
|
|
|
|
**Use Cases:**
|
|
- Audit logging: publish every operation for compliance
|
|
- Real-time UI updates: push results to web dashboards
|
|
- Workflow coordination: trigger dependent operations
|
|
|
|
### 4. Dual-Mode Execution
|
|
|
|
**HTTP Daemon Mode (Default):**
|
|
```
|
|
provctl --daemon
|
|
↓
|
|
HTTP request to local daemon (provd)
|
|
↓
|
|
Daemon executes via OperationRegistry
|
|
↓
|
|
Response returned to client
|
|
```
|
|
|
|
**Offline Mode (Embedded):**
|
|
```
|
|
provctl --offline
|
|
↓
|
|
Create OfflineMode executor directly
|
|
↓
|
|
Execute operations in-process
|
|
↓
|
|
Return results immediately
|
|
```
|
|
|
|
---
|
|
|
|
## Extension Points
|
|
|
|
### Adding a New Operation
|
|
|
|
**Step 1: Implement `EcosystemOperation`**
|
|
|
|
```rust
|
|
// In your crate's main lib.rs
|
|
use daemon_cli::contracts::stable::EcosystemOperation;
|
|
use async_trait::async_trait;
|
|
|
|
pub struct MyCustomOperation;
|
|
|
|
#[async_trait]
|
|
impl EcosystemOperation for MyCustomOperation {
|
|
fn name(&self) -> &'static str { "my-custom" }
|
|
|
|
async fn execute(&self, params: serde_json::Value)
|
|
-> daemon_cli::Result<OperationResult> {
|
|
// Your implementation
|
|
Ok(OperationResult {
|
|
status: "success".into(),
|
|
data: json!({ "result": "data" }),
|
|
})
|
|
}
|
|
|
|
fn cache_key(&self, params: &serde_json::Value) -> String {
|
|
format!("my-custom:{}", params["input"])
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: Register with Registry**
|
|
|
|
```rust
|
|
// In daemon-cli's setup code
|
|
use daemon_cli::contracts::stable::OperationRegistry;
|
|
|
|
let registry = OperationRegistry::new();
|
|
registry.register(Arc::new(MyCustomOperation));
|
|
```
|
|
|
|
**Step 3: Access via API or CLI**
|
|
|
|
```bash
|
|
# Via REST API
|
|
curl -X POST http://localhost:9999/execute \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"operation": "my-custom", "params": {"input": "value"}}'
|
|
|
|
# Via CLI
|
|
provctl execute my-custom --input value
|
|
```
|
|
|
|
### Adding Health Checks
|
|
|
|
**Implement custom health probe:**
|
|
|
|
```rust
|
|
use daemon_cli::contracts::observability::{HealthProbe, ProbeStatus};
|
|
|
|
struct MyServiceProbe;
|
|
|
|
#[async_trait]
|
|
impl HealthProbe for MyServiceProbe {
|
|
fn name(&self) -> &str { "my-service" }
|
|
|
|
async fn check(&self) -> ProbeStatus {
|
|
if my_service_is_healthy().await {
|
|
ProbeStatus::Healthy
|
|
} else {
|
|
ProbeStatus::Unhealthy("Service unavailable".into())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register probe
|
|
daemon.register_probe(Box::new(MyServiceProbe));
|
|
```
|
|
|
|
**Kubernetes Usage:**
|
|
|
|
```yaml
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health/live
|
|
port: 9999
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health/ready
|
|
port: 9999
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
```
|
|
|
|
### Adding Webhook Handlers
|
|
|
|
**Implement webhook processor:**
|
|
|
|
```rust
|
|
use daemon_cli::contracts::integration::WebHookEvent;
|
|
|
|
async fn handle_github_webhook(event: WebHookEvent) {
|
|
match event.event_type.as_str() {
|
|
"push" => {
|
|
let branch = event.payload["ref"].as_str().unwrap_or("unknown");
|
|
println!("Push to {}", branch);
|
|
// Trigger deployment, run tests, etc.
|
|
}
|
|
"pull_request" => {
|
|
println!("PR event: {:?}", event.payload["action"]);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Register webhook
|
|
daemon.register_webhook_handler("github", Box::new(handle_github_webhook)).await?;
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Contracts for Imports
|
|
|
|
```rust
|
|
// ✅ Good: Explicit stability contract
|
|
use daemon_cli::contracts::stable::EcosystemOperation;
|
|
use daemon_cli::contracts::observability::HealthProbe;
|
|
|
|
// ⚠️ Acceptable: Direct import (less clear about stability)
|
|
use daemon_cli::EcosystemOperation;
|
|
|
|
// ❌ Avoid: Implementation details
|
|
use daemon_cli::cache::CacheEntry;
|
|
```
|
|
|
|
### 2. Handle Errors Properly
|
|
|
|
```rust
|
|
use daemon_cli::contracts::stable::Result;
|
|
|
|
#[async_trait]
|
|
impl EcosystemOperation for MyOp {
|
|
async fn execute(&self, params: serde_json::Value) -> Result<OperationResult> {
|
|
// Use ? operator for propagation
|
|
let value = params["required_field"]
|
|
.as_str()
|
|
.ok_or(DaemonError::validation("required_field is required"))?;
|
|
|
|
Ok(OperationResult { status: "success".into(), data: json!({}) })
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Implement Caching for Performance
|
|
|
|
```rust
|
|
fn cache_key(&self, params: &serde_json::Value) -> String {
|
|
// Include all inputs that affect output
|
|
format!(
|
|
"my-op:{}:{}",
|
|
params["input_a"],
|
|
params["input_b"]
|
|
)
|
|
}
|
|
```
|
|
|
|
### 4. Publish Events for Observability
|
|
|
|
```rust
|
|
#[async_trait]
|
|
impl EcosystemOperation for MyOp {
|
|
async fn execute(&self, params: serde_json::Value) -> Result<OperationResult> {
|
|
// Publish start event
|
|
bus.publish(Event {
|
|
event_type: EventType::OperationStarted,
|
|
payload: EventPayload::Operation { name: self.name().into(), params },
|
|
});
|
|
|
|
let result = self.do_work().await?;
|
|
|
|
// Publish completion event
|
|
bus.publish(Event {
|
|
event_type: EventType::OperationCompleted,
|
|
payload: EventPayload::OperationResult { name: self.name().into(), result: json!(result) },
|
|
});
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Support Configuration Rendering
|
|
|
|
```rust
|
|
use daemon_cli::contracts::execution::{ConfigRenderer, RenderRequest};
|
|
|
|
async fn render_config(&self, params: serde_json::Value) -> Result<String> {
|
|
let renderer = ConfigRenderer::new();
|
|
let request = RenderRequest {
|
|
language: ConfigLanguage::Tera,
|
|
template: params["template"].as_str().unwrap().into(),
|
|
context: params["context"].clone(),
|
|
};
|
|
|
|
let response = renderer.render(&request).await?;
|
|
Ok(response.rendered_output)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Integration Examples
|
|
|
|
### Example 1: Implementing a Custom Validator
|
|
|
|
**Goal:** Add a new validation operation for Helm charts.
|
|
|
|
```rust
|
|
// my_validator crate
|
|
use daemon_cli::contracts::stable::EcosystemOperation;
|
|
use async_trait::async_trait;
|
|
|
|
pub struct HelmValidatorOp;
|
|
|
|
#[async_trait]
|
|
impl EcosystemOperation for HelmValidatorOp {
|
|
fn name(&self) -> &'static str { "helm-validator" }
|
|
|
|
async fn execute(&self, params: serde_json::Value) -> Result<OperationResult> {
|
|
let chart_path = params["chart_path"].as_str()?;
|
|
|
|
// Validate Helm chart
|
|
let is_valid = validate_helm_chart(chart_path).await?;
|
|
|
|
Ok(OperationResult {
|
|
status: if is_valid { "success" } else { "failed" }.into(),
|
|
data: json!({
|
|
"valid": is_valid,
|
|
"chart": chart_path,
|
|
}),
|
|
})
|
|
}
|
|
|
|
fn cache_key(&self, params: &serde_json::Value) -> String {
|
|
format!("helm-validator:{}", params["chart_path"])
|
|
}
|
|
}
|
|
```
|
|
|
|
**Register in daemon-cli:**
|
|
```rust
|
|
registry.register(Arc::new(HelmValidatorOp));
|
|
```
|
|
|
|
**Use via CLI:**
|
|
```bash
|
|
provctl execute helm-validator --chart-path ./my-chart/
|
|
```
|
|
|
|
### Example 2: Publishing Events to External System
|
|
|
|
**Goal:** Send operation results to a monitoring system.
|
|
|
|
```rust
|
|
use daemon_cli::contracts::stable::EventBus;
|
|
|
|
pub struct MonitoringBridge {
|
|
bus: Arc<EventBus>,
|
|
remote_url: String,
|
|
}
|
|
|
|
impl MonitoringBridge {
|
|
pub async fn start(self) {
|
|
let mut subscriber = self.bus.subscribe();
|
|
|
|
while let Some(event) = subscriber.recv().await {
|
|
// Forward to remote monitoring system
|
|
self.send_to_monitoring(&event).await.ok();
|
|
}
|
|
}
|
|
|
|
async fn send_to_monitoring(&self, event: &Event) -> Result<()> {
|
|
let client = reqwest::Client::new();
|
|
client
|
|
.post(&format!("{}/events", self.remote_url))
|
|
.json(event)
|
|
.send()
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
### Example 3: Health Check for Custom Service
|
|
|
|
```rust
|
|
use daemon_cli::contracts::observability::{HealthProbe, ProbeStatus};
|
|
|
|
pub struct DatabaseProbe {
|
|
connection_string: String,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl HealthProbe for DatabaseProbe {
|
|
fn name(&self) -> &str { "database" }
|
|
|
|
async fn check(&self) -> ProbeStatus {
|
|
match connect_to_db(&self.connection_string).await {
|
|
Ok(_) => ProbeStatus::Healthy,
|
|
Err(e) => ProbeStatus::Unhealthy(format!("DB connection failed: {}", e)),
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Versioning & Compatibility
|
|
|
|
### Semantic Versioning
|
|
|
|
- **Major (0.x.y → 1.0.0)**: Breaking changes to stable API
|
|
- **Minor (1.x.y → 1.1.0)**: New features (backwards compatible)
|
|
- **Patch (1.0.x → 1.0.1)**: Bug fixes
|
|
|
|
### Stable API Guarantees
|
|
|
|
Within a major version:
|
|
- ✅ New fields may be added to structs (with defaults)
|
|
- ✅ New methods may be added to traits
|
|
- ❌ Existing fields/methods will not be removed or changed
|
|
- ❌ Stable types will not move to different modules
|
|
|
|
### Internal Module Changes
|
|
|
|
Between any versions:
|
|
- Internal modules (not in `contracts`) may:
|
|
- Change function signatures
|
|
- Reorganize code
|
|
- Be replaced with different implementations
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Operation Not Found
|
|
|
|
**Problem:** `OperationError::OperationNotFound("my-op")`
|
|
|
|
**Solution:** Ensure operation is registered before use.
|
|
```rust
|
|
registry.register(Arc::new(MyOp)); // Must happen before daemon starts
|
|
```
|
|
|
|
### Cache Stale Data
|
|
|
|
**Problem:** Operation returns old results.
|
|
|
|
**Solution:** Clear cache or use different cache key.
|
|
```rust
|
|
fn cache_key(&self, params: &serde_json::Value) -> String {
|
|
// Include version or timestamp to invalidate when needed
|
|
format!("my-op:{}:v{}", params["input"], params["version"])
|
|
}
|
|
```
|
|
|
|
### Webhook Events Not Received
|
|
|
|
**Problem:** WebHook handler never called.
|
|
|
|
**Solution:** Verify webhook is registered and endpoint is reachable.
|
|
```rust
|
|
daemon.register_webhook("github", handler).await?;
|
|
// Then configure webhook in GitHub to point to: http://daemon-host:9999/webhooks/github
|
|
```
|
|
|
|
### Health Check Always Fails
|
|
|
|
**Problem:** Readiness probe always returning Unhealthy.
|
|
|
|
**Solution:** Verify probe logic and dependencies are available.
|
|
```rust
|
|
async fn check(&self) -> ProbeStatus {
|
|
// Must complete within timeout
|
|
match timeout(Duration::from_secs(5), self.check_impl()).await {
|
|
Ok(result) => result,
|
|
Err(_) => ProbeStatus::Unhealthy("Check timed out".into()),
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- **EcosystemOperation**: Extension point for operations
|
|
- **EventBus**: Real-time event distribution
|
|
- **OperationRegistry**: Operation discovery and execution
|
|
- **AppState**: Shared state across daemon
|
|
- **contracts module**: Stable API definitions
|
|
|