rust_supervisor/policy/group.rs
1//! Group isolation strategy module.
2//!
3//! Implements group-level fault boundary enforcement
4//! with dependency edge propagation rules (US2: group fault stays within boundary).
5//!
6//! [`GroupDependencyEdge`] declares a directed failure propagation relationship
7//! between two groups. [`PropagationPolicy`] controls the propagation semantics:
8//! `None` (fully isolated), `EscalateOnly` (notify parent, don't block children),
9//! or `Full` (mark all children in the dependent group as non-restartable).
10//!
11//! [`GroupIsolationPolicy`] evaluates whether a failure in one group affects
12//! another, based on the declared DAG of dependency edges. Cyclic dependencies
13//! are rejected at config load time.
14
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17
18/// Failure propagation policy across group boundaries.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
20pub enum PropagationPolicy {
21 /// No propagation — groups are fully isolated.
22 None,
23 /// Escalate to parent supervisor only, do not affect current group.
24 EscalateOnly,
25 /// Full propagation — all children in the current group are marked
26 /// non-restartable and the group enters meltdown.
27 /// Propagation direction: fault flows from `to_group` to `from_group`
28 /// (one-way), never reversed.
29 Full,
30}
31
32/// Declares a failure propagation dependency between groups.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
34pub struct GroupDependencyEdge {
35 /// The group that depends on another group.
36 pub from_group: String,
37 /// The group that is depended on.
38 pub to_group: String,
39 /// How failures propagate from `to_group` to `from_group`.
40 pub propagation: PropagationPolicy,
41}
42
43/// Evaluates whether a failure in one group affects another group.
44///
45/// Dependency edges form a directed acyclic graph (DAG). Cyclic dependencies
46/// are detected at config load time and rejected with a structured error
47/// listing the group names on the cycle.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct GroupIsolationPolicy {
50 /// Declared cross-group dependency edges.
51 dependencies: Vec<GroupDependencyEdge>,
52}
53
54impl GroupIsolationPolicy {
55 /// Creates an isolation policy from declared dependency edges.
56 pub fn new(dependencies: Vec<GroupDependencyEdge>) -> Self {
57 Self { dependencies }
58 }
59
60 /// Checks whether `my_group` is affected by a failure in `failed_group`.
61 ///
62 /// Returns `true` when a dependency edge explicitly allows propagation,
63 /// or when `my_group` is the same as `failed_group`.
64 pub fn affected_by(&self, my_group: &str, failed_group: &str) -> bool {
65 if my_group == failed_group {
66 return true;
67 }
68 self.dependencies.iter().any(|edge| {
69 edge.from_group == my_group
70 && edge.to_group == failed_group
71 && edge.propagation == PropagationPolicy::Full
72 })
73 }
74}