⎇ f4eb7dd94404c2fadd8099c61ca11d2860bcff7a
crates/ente-zero/src/graph/mod.rs
History
Who touched what
raw
Capability quotas, CAS gc, patrones burst/silence, snapshot incremental
1//! `EnteGraph`: estado del fractal vivo en PID 1.
2//!
3//! Diseño:
4//! - Submódulos por concern: lifecycle, topology, shutdown, bus_mediator,
5//! devices, capabilities. Cada uno extiende `impl EnteGraph` con métodos
6//! relacionados.
7//! - Estado plano (no substructs todavía) — la separación es por
8//! comportamiento, no por compartimentación de datos.
9//! - Toda mutación pasa por el bucle primordial vía `GraphEvent`. Los
10//! submódulos se llaman desde `main.rs::primordial_loop`.
11
12mod bus_mediator;
13mod capabilities;
14mod devices;
15mod lifecycle;
16mod shutdown;
17mod topology;
18
19use ente_bus::{BusMessage, BusResponse};
20use ente_card::{Capability, EntityCard};
21use nix::unistd::Pid;
22use std::collections::{BTreeMap, BTreeSet, HashMap};
23use tokio::sync::{mpsc, oneshot};
24use ulid::Ulid;
25
26pub use shutdown::SHUTDOWN_GRACE;
27
28/// Bit alto encendido en `seq` para invokes server-iniciados — evita choque
29/// con secuencias allocadas por clientes.
30pub(in crate::graph) const SERVER_SEQ_FLAG: u64 = 1u64 << 63;
31
32pub struct EnteGraph {
33 pub(in crate::graph) seed: EntityCard,
34 /// Entes encarnados como proceso o nodo virtual. id↔pid bidireccional.
35 pub(in crate::graph) incarnated: HashMap<Ulid, Incarnated>,
36 pub(in crate::graph) by_pid: HashMap<i32, Ulid>,
37 /// Quién provee qué capacidad. Resuelve `requires` y `pick_invokable`.
38 pub(in crate::graph) providers: BTreeMap<Capability, BTreeSet<Ulid>>,
39 /// Tokens de capability emitidos. Revocables al morir el proveedor.
40 pub(in crate::graph) next_token: u64,
41 pub(in crate::graph) grants: HashMap<u64, GrantedCapability>,
42 /// Dispositivos del kernel presentes (devpath → última UEvent).
43 pub(in crate::graph) devices: HashMap<String, ente_kernel::UEvent>,
44 /// Cards genesis pendientes de instanciar (extraídas de la Semilla).
45 pub(in crate::graph) pending_genesis: Vec<EntityCard>,
46 /// Hijos directos por lineage. parent → [child, ...].
47 pub(in crate::graph) children: HashMap<Ulid, Vec<Ulid>>,
48 /// Conexiones del bus indexadas por la identidad anunciada y verificada
49 /// con SO_PEERCRED. El value es el extremo de escritura del writer task.
50 pub(in crate::graph) bus_connections: HashMap<Ulid, mpsc::Sender<BusMessage>>,
51 /// Invokes forwardeados pendientes de respuesta del proveedor.
52 pub(in crate::graph) pending_invokes: HashMap<u64, oneshot::Sender<BusResponse>>,
53 pub(in crate::graph) next_invoke_seq: u64,
54}
55
56pub(in crate::graph) struct Incarnated {
57 pub card: EntityCard,
58 pub pid: Option<Pid>,
59 /// Capacidades añadidas en runtime vía BusRequest::UpdateCapabilities.
60 /// La Card original es immutable; la "vista efectiva" del Ente es
61 /// `card.provides ∪ dynamic_provides`.
62 pub dynamic_provides: BTreeSet<Capability>,
63}
64
65pub(in crate::graph) struct GrantedCapability {
66 pub cap: Capability,
67 pub provider: Ulid,
68 pub holder: Ulid,
69 /// Instante en el que el grant deja de ser válido. El garbage collector
70 /// del cerebro purga grants con `Instant::now() > expires_at`.
71 pub expires_at: std::time::Instant,
72}
73
74/// TTL default para grants cuando la cap no tiene override. 60s es un
75/// compromiso: largo enough para evitar churn en patrones interactivos,
76/// corto enough para que credenciales filtradas expiren rápidamente.
77pub const DEFAULT_GRANT_TTL: std::time::Duration = std::time::Duration::from_secs(60);
78
79/// Quota máxima de tokens activos por (holder, cap). Caps escaladas tienen
80/// quota baja para limitar fugas de credenciales; caps de uso frecuente
81/// (Endpoint, Journal) son más laxas.
82pub fn quota_for_capability(cap: &Capability) -> u32 {
83 match cap {
84 // Caps escaladas: pocos tokens, fuerza patrón request-act-release.
85 Capability::Spawn => 2,
86 Capability::FilesystemRoot => 2,
87 Capability::Device { .. } => 4,
88 // Caps de propósito general.
89 Capability::Endpoint { .. } => 16,
90 Capability::KernelNetlink(_) => 4,
91 Capability::LegacyLogind => 8,
92 // Logging: hasta 32 streams.
93 Capability::Journal => 32,
94 }
95}
96
97/// TTL específico por variante de Capability. Caps de mayor riesgo / costo
98/// (Spawn, FilesystemRoot) tienen TTL más corto; caps "logging" como
99/// Journal pueden vivir más.
100///
101/// Cualquier cap no listada cae al `DEFAULT_GRANT_TTL`.
102pub fn ttl_for_capability(cap: &Capability) -> std::time::Duration {
103 use std::time::Duration;
104 match cap {
105 // Caps escaladas: TTL corto para forzar renovación frecuente.
106 Capability::Spawn => Duration::from_secs(30),
107 Capability::FilesystemRoot => Duration::from_secs(30),
108 Capability::Device { .. } => Duration::from_secs(60),
109 // Caps de propósito general.
110 Capability::Endpoint { .. } => Duration::from_secs(300), // 5 min
111 Capability::KernelNetlink(_) => Duration::from_secs(300),
112 Capability::LegacyLogind => Duration::from_secs(300),
113 // Logging puede vivir mucho.
114 Capability::Journal => Duration::from_secs(3600), // 1h
115 }
116}
117
118impl EnteGraph {
119 pub fn new(mut seed: EntityCard) -> Self {
120 // Extraemos genesis antes de almacenar la Semilla — evita duplicación
121 // en `incarnated[seed.id]`.
122 let pending_genesis = std::mem::take(&mut seed.genesis);
123 let mut g = Self {
124 seed: seed.clone(),
125 incarnated: HashMap::new(),
126 by_pid: HashMap::new(),
127 providers: BTreeMap::new(),
128 next_token: 1,
129 grants: HashMap::new(),
130 devices: HashMap::new(),
131 pending_genesis,
132 children: HashMap::new(),
133 bus_connections: HashMap::new(),
134 pending_invokes: HashMap::new(),
135 next_invoke_seq: 0,
136 };
137 // El Ente #0 se inscribe a sí mismo como proveedor de las capacidades
138 // que su Card declara — sólo así los hijos pueden requerirlas.
139 g.register_provider(&seed);
140 g.incarnated.insert(seed.id, Incarnated {
141 card: seed, pid: None,
142 dynamic_provides: BTreeSet::new(),
143 });
144 g
145 }
146
147 pub fn lookup_pid(&self, pid: Pid) -> Option<Ulid> {
148 self.by_pid.get(&pid.as_raw()).copied()
149 }
150
151 /// Acceso read-only a la Card de un Ente vivo. Usado por el cerebro
152 /// para hidratar `SubjectInfo` sin clonar todo el mapa.
153 pub fn peek_card(&self, id: &Ulid) -> Option<&EntityCard> {
154 self.incarnated.get(id).map(|i| &i.card)
155 }
156
157 /// Identidad de la Semilla. Usado como `requester` para spawns generados
158 /// por reglas auto-cristalizadas (única identidad con Capability::Spawn).
159 pub fn seed_id(&self) -> Ulid {
160 self.seed.id
161 }
162
163 /// Captura el estado live como snapshot serializable. Excluye la Semilla
164 /// (será re-sintetizada al restore con su seed_id preservado).
165 pub fn snapshot(&self) -> ente_snapshot::FractalSnapshot {
166 let entes: Vec<EntityCard> = self.incarnated.iter()
167 .filter(|(id, _)| **id != self.seed.id)
168 .map(|(_, inc)| inc.card.clone())
169 .collect();
170 ente_snapshot::FractalSnapshot {
171 version: ente_snapshot::SNAPSHOT_VERSION,
172 timestamp_ms: ente_snapshot::now_ms(),
173 seed_id: self.seed.id,
174 seed_label: self.seed.label.clone(),
175 entes,
176 }
177 }
178
179 pub(in crate::graph) fn register_provider(&mut self, card: &EntityCard) {
180 for cap in &card.provides {
181 self.providers.entry(cap.clone()).or_default().insert(card.id);
182 }
183 }
184
185 pub(in crate::graph) fn unregister_provider(&mut self, card: &EntityCard) {
186 for cap in &card.provides {
187 if let Some(set) = self.providers.get_mut(cap) {
188 set.remove(&card.id);
189 }
190 }
191 // Revocar grants emitidos por el Ente fallecido.
192 let revoked: Vec<u64> = self.grants.iter()
193 .filter(|(_, g)| g.provider == card.id)
194 .map(|(t, _)| *t)
195 .collect();
196 for t in revoked {
197 self.grants.remove(&t);
198 }
199 }
200
201 /// Quita una capacidad dinámica del índice de providers para un Ente
202 /// específico. Usado al recibir UpdateCapabilities con `removes`.
203 pub(in crate::graph) fn unregister_dynamic_cap(&mut self, ente_id: Ulid, cap: &Capability) {
204 if let Some(set) = self.providers.get_mut(cap) {
205 set.remove(&ente_id);
206 }
207 }
208
209 /// Inserta una capacidad dinámica al índice de providers para un Ente.
210 pub(in crate::graph) fn register_dynamic_cap(&mut self, ente_id: Ulid, cap: Capability) {
211 self.providers.entry(cap).or_default().insert(ente_id);
212 }
213
214 /// El Ente #0 (semilla) tiene todas sus capacidades declaradas. Otros
215 /// las tienen si su Card las declara o si poseen un grant vivo.
216 pub(in crate::graph) fn holder_has(&self, holder: Ulid, cap: &Capability) -> bool {
217 if holder == self.seed.id {
218 return self.seed.provides.contains(cap);
219 }
220 if let Some(inc) = self.incarnated.get(&holder) {
221 if inc.card.provides.contains(cap) { return true; }
222 }
223 self.grants.values().any(|g| g.holder == holder && &g.cap == cap)
224 }
225}