rust-tokio-supervisor 0.1.4

A Rust tokio supervisor with declarative task supervision, restart policy, shutdown coordination, and observability.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//! Public configuration input model for supervisor users.
//!
//! The structs in this module are the single raw configuration surface used for
//! YAML loading, template rendering, and JSON Schema generation.

use confique::Config;
use rust_config_tree::ConfigSchema;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use crate::{
    config::{
        audit::AuditConfig,
        ipc_security::IpcSecurityConfig,
        policy::{
            ChildStrategyOverrideConfig, DynamicSupervisorConfig, FailureWindowConfig,
            GroupDependencyConfig, GroupStrategyConfig, GroupsConfigSection, MeltdownConfig,
            RestartBudgetConfig, SeverityDefaultConfig, SupervisionPipelineConfig,
        },
    },
    spec::{
        child_declaration::ChildrenConfigSection,
        supervisor::{
            BackpressureConfig, EscalationPolicy, RECOMMENDED_CHANNEL_CAPACITY, SupervisionStrategy,
        },
    },
};

/// Configuration file shape loaded from YAML.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema, ConfigSchema)]
pub struct SupervisorConfig {
    /// Additional configuration files included by `rust-config-tree`.
    #[config(default = [])]
    #[serde(default)]
    pub include: Vec<PathBuf>,

    /// Root supervisor declaration values.
    #[config(nested)]
    pub supervisor: SupervisorRootConfig,

    /// Runtime policy values.
    #[config(nested)]
    #[serde(default)]
    pub policy: PolicyConfig,

    /// Shutdown budget values.
    #[config(nested)]
    #[serde(default)]
    pub shutdown: ShutdownConfig,

    /// Observability switches and capacities.
    #[config(nested)]
    #[serde(default)]
    pub observability: ObservabilityConfig,

    /// Command audit persistence configuration.
    #[config(nested)]
    #[serde(default)]
    pub audit: AuditConfig,

    /// Backpressure policy for observability event subscribers.
    #[config(nested)]
    #[serde(default)]
    pub backpressure: BackpressureConfig,

    /// Group-level restart budgets and group policy declarations.
    #[config(nested)]
    #[serde(default)]
    #[schemars(extend("x-tree-split" = true, "x-tree-transparent-array" = true))]
    pub groups: GroupsConfigSection,

    /// Group-level strategy overrides.
    #[config(default = [])]
    #[serde(default)]
    pub group_strategies: Vec<GroupStrategyConfig>,

    /// Cross-group failure propagation dependencies.
    #[config(default = [])]
    #[serde(default)]
    pub group_dependencies: Vec<GroupDependencyConfig>,

    /// Child-level strategy overrides.
    #[config(default = [])]
    #[serde(default)]
    pub child_strategy_overrides: Vec<ChildStrategyOverrideConfig>,

    /// Default severity class per task role.
    #[config(default = [])]
    #[serde(default)]
    pub severity_defaults: Vec<SeverityDefaultConfig>,

    /// Optional target-side dashboard IPC configuration.
    pub dashboard: Option<DashboardIpcConfig>,

    /// Child declarations loaded from YAML.
    #[config(nested)]
    #[serde(default)]
    #[schemars(extend("x-tree-split" = true, "x-tree-transparent-array" = true))]
    pub children: ChildrenConfigSection,
}

// impl ConfigSchema for SupervisorConfig {
//     /// Returns child configuration paths declared by one loaded layer.
//     ///
//     /// # Arguments
//     ///
//     /// - `layer`: Partially loaded supervisor configuration layer.
//     ///
//     /// # Returns
//     ///
//     /// Returns include paths declared by this configuration layer.
//     fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
//         layer.include.clone().unwrap_or_default()
//     }
// }

/// Root supervisor configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
pub struct SupervisorRootConfig {
    /// Restart scope strategy for child failures.
    #[config(default = "OneForAll")]
    #[serde(default = "default_supervision_strategy")]
    pub strategy: SupervisionStrategy,
    /// Optional fallback escalation policy used when child and group policies
    /// do not define a more specific policy.
    ///
    /// Root supervisors do not have a parent supervisor at runtime, so
    /// `escalate_to_parent` at root level is a planning and diagnostic label.
    #[schemars(!default)]
    #[serde(default)]
    pub escalation_policy: Option<EscalationPolicy>,
    /// Control command channel capacity.
    ///
    /// Runtime capacity for queued supervisor control commands.
    /// Recommended default: 256.
    #[config(default = 256)]
    #[serde(default = "default_channel_capacity")]
    pub control_channel_capacity: usize,
    /// Event broadcast channel capacity.
    ///
    /// Runtime capacity for supervisor event broadcast delivery.
    /// Recommended default: 256.
    #[config(default = 256)]
    #[serde(default = "default_channel_capacity")]
    pub event_channel_capacity: usize,
    /// Runtime dynamic child acceptance policy.
    #[config(nested)]
    #[serde(default)]
    pub dynamic_supervisor: DynamicSupervisorConfig,
}

/// Restart, backoff, and fuse configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
pub struct PolicyConfig {
    /// Maximum child restarts within the child restart window.
    #[config(default = 10)]
    #[serde(default = "default_child_restart_limit")]
    pub child_restart_limit: u32,
    /// Child restart window in milliseconds.
    #[config(default = 60000)]
    #[serde(default = "default_child_restart_window_ms")]
    pub child_restart_window_ms: u64,
    /// Maximum supervisor failures within the supervisor failure window.
    #[config(default = 30)]
    #[serde(default = "default_supervisor_failure_limit")]
    pub supervisor_failure_limit: u32,
    /// Supervisor failure window in milliseconds.
    #[config(default = 60000)]
    #[serde(default = "default_supervisor_failure_window_ms")]
    pub supervisor_failure_window_ms: u64,
    /// Initial backoff in milliseconds.
    #[config(default = 100)]
    #[serde(default = "default_initial_backoff_ms")]
    pub initial_backoff_ms: u64,
    /// Maximum backoff in milliseconds.
    #[config(default = 5000)]
    #[serde(default = "default_max_backoff_ms")]
    pub max_backoff_ms: u64,
    /// Jitter ratio expressed as a fraction between zero and one.
    #[config(default = 0.10)]
    #[serde(default = "default_jitter_ratio")]
    pub jitter_ratio: f64,
    /// Heartbeat interval in milliseconds.
    #[config(default = 1000)]
    #[serde(default = "default_heartbeat_interval_ms")]
    pub heartbeat_interval_ms: u64,
    /// Stale heartbeat threshold in milliseconds.
    #[config(default = 3000)]
    #[serde(default = "default_stale_after_ms")]
    pub stale_after_ms: u64,
    /// Restart budget used by the supervision pipeline.
    #[config(nested)]
    #[serde(default)]
    pub restart_budget: RestartBudgetConfig,
    /// Failure window used by the supervision pipeline.
    #[config(nested)]
    #[serde(default)]
    pub failure_window: FailureWindowConfig,
    /// Meltdown fuse limits for child, group, and supervisor scopes.
    #[config(nested)]
    #[serde(default)]
    pub meltdown: MeltdownConfig,
    /// Supervision pipeline capacities and concurrent restart limit.
    #[config(nested)]
    #[serde(default)]
    pub supervision_pipeline: SupervisionPipelineConfig,
}

/// Shutdown coordination configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
pub struct ShutdownConfig {
    /// Graceful drain timeout in milliseconds.
    #[config(default = 5000)]
    #[serde(default = "default_graceful_timeout_ms")]
    pub graceful_timeout_ms: u64,
    /// Abort wait timeout in milliseconds.
    #[config(default = 1000)]
    #[serde(default = "default_abort_wait_ms")]
    pub abort_wait_ms: u64,
}

/// Observability configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
pub struct ObservabilityConfig {
    /// Event journal capacity.
    #[config(default = 256)]
    #[serde(default = "default_event_journal_capacity")]
    pub event_journal_capacity: usize,
    /// Whether metrics recording is enabled.
    #[config(default = true)]
    #[serde(default = "default_true")]
    pub metrics_enabled: bool,
    /// Whether command audit recording is enabled.
    #[config(default = true)]
    #[serde(default = "default_true")]
    pub audit_enabled: bool,
}

impl Default for PolicyConfig {
    /// Returns the default runtime policy configuration.
    fn default() -> Self {
        Self {
            child_restart_limit: default_child_restart_limit(),
            child_restart_window_ms: default_child_restart_window_ms(),
            supervisor_failure_limit: default_supervisor_failure_limit(),
            supervisor_failure_window_ms: default_supervisor_failure_window_ms(),
            initial_backoff_ms: default_initial_backoff_ms(),
            max_backoff_ms: default_max_backoff_ms(),
            jitter_ratio: default_jitter_ratio(),
            heartbeat_interval_ms: default_heartbeat_interval_ms(),
            stale_after_ms: default_stale_after_ms(),
            restart_budget: RestartBudgetConfig::default(),
            failure_window: FailureWindowConfig::default(),
            meltdown: MeltdownConfig::default(),
            supervision_pipeline: SupervisionPipelineConfig::default(),
        }
    }
}

impl Default for ShutdownConfig {
    /// Returns the default shutdown coordination configuration.
    fn default() -> Self {
        Self {
            graceful_timeout_ms: default_graceful_timeout_ms(),
            abort_wait_ms: default_abort_wait_ms(),
        }
    }
}

impl Default for ObservabilityConfig {
    /// Returns the default observability configuration.
    fn default() -> Self {
        Self {
            event_journal_capacity: default_event_journal_capacity(),
            metrics_enabled: default_true(),
            audit_enabled: default_true(),
        }
    }
}

/// Returns the default supervision strategy.
fn default_supervision_strategy() -> SupervisionStrategy {
    SupervisionStrategy::OneForAll
}

/// Returns the default child restart limit.
fn default_child_restart_limit() -> u32 {
    10
}

/// Returns the default child restart window in milliseconds.
fn default_child_restart_window_ms() -> u64 {
    60000
}

/// Returns the default supervisor failure limit.
fn default_supervisor_failure_limit() -> u32 {
    30
}

/// Returns the default supervisor failure window in milliseconds.
fn default_supervisor_failure_window_ms() -> u64 {
    60000
}

/// Returns the default initial backoff in milliseconds.
fn default_initial_backoff_ms() -> u64 {
    100
}

/// Returns the default maximum backoff in milliseconds.
fn default_max_backoff_ms() -> u64 {
    5000
}

/// Returns the default jitter ratio.
fn default_jitter_ratio() -> f64 {
    0.10
}

/// Returns the default heartbeat interval in milliseconds.
fn default_heartbeat_interval_ms() -> u64 {
    1000
}

/// Returns the default stale heartbeat threshold in milliseconds.
fn default_stale_after_ms() -> u64 {
    3000
}

/// Returns the default graceful shutdown timeout in milliseconds.
fn default_graceful_timeout_ms() -> u64 {
    5000
}

/// Returns the default abort wait timeout in milliseconds.
fn default_abort_wait_ms() -> u64 {
    1000
}

/// Returns the recommended channel capacity.
fn default_channel_capacity() -> usize {
    RECOMMENDED_CHANNEL_CAPACITY
}

/// Returns the default event journal capacity.
fn default_event_journal_capacity() -> usize {
    256
}

/// Serde default helper: returns true.
fn default_true() -> bool {
    true
}

/// Optional target-side dashboard IPC configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config, JsonSchema)]
pub struct DashboardIpcConfig {
    /// Whether the target process opens the local IPC endpoint.
    pub enabled: bool,
    /// Stable target process identifier sent to relay and UI.
    pub target_id: Option<String>,
    /// Local Unix domain socket path used by the target process.
    pub path: Option<PathBuf>,
    /// Socket file permission string such as `0600`.
    pub permissions: Option<String>,
    /// Socket bind behavior when the path already exists.
    pub bind_mode: Option<DashboardIpcBindMode>,
    /// Dynamic registration settings used after IPC is ready.
    pub registration: Option<DashboardRegistrationConfig>,
    /// Optional IPC security pipeline configuration (C1-C9).
    #[schemars(!default)]
    #[serde(default)]
    pub security_config: Option<IpcSecurityConfig>,
}

/// Socket bind behavior for target-side dashboard IPC.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardIpcBindMode {
    /// Fail when the socket path already exists.
    CreateNew,
    /// Remove a stale socket path before binding.
    ReplaceStale,
}

/// Dynamic registration settings for a target process.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Config, JsonSchema)]
pub struct DashboardRegistrationConfig {
    /// Whether the target process registers with relay after IPC is ready.
    pub enabled: bool,
    /// Local relay registration socket path.
    pub relay_registration_path: Option<PathBuf>,
    /// Human-readable name shown in the dashboard.
    pub display_name: Option<String>,
    /// Registration lease duration in seconds.
    pub lease_seconds: Option<u64>,
    /// Registration heartbeat interval in seconds.
    pub registration_heartbeat_interval_seconds: Option<u64>,
    /// Timeout in seconds for connecting to the relay registration socket.
    /// Default: 5.
    pub registration_connect_timeout_secs: Option<u64>,
    /// Timeout in seconds for write and ack-read on the registration socket.
    /// Default: 5.
    pub registration_io_timeout_secs: Option<u64>,
}