Capability quotas, CAS gc, patrones burst/silence, snapshot incremental
- quota_for_capability(cap) tabla por variante: Spawn 2, FilesystemRoot 2, Endpoint 16, Journal 32. mediate_capability rechaza con QuotaExceeded si holder ya tiene N tokens activos. - ente_cas::list_all_shas() + gc(reachable). audit::reachable_from_head() walks la cadena. Endpoint GcCas con extra_roots para Wasm SHAs. brainctl gc-cas. - PatternCrystal::Burst (kind, count, freq_hz) y Silence (kind, since_secs). detect_pattern_crystals + endpoint PatternCrystals + brainctl patterns. - Observer.dirty_marginal/dirty_cooccur tracking. snapshot() marca consumo (clears dirty); snapshot_delta() emite sólo lo cambiado. apply_delta() merges incremental sobre estado existente. Útil para checkpoints frecuentes con poco overhead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,4 +52,7 @@ pub enum CapabilityGrant {
|
||||
Granted { token: u64 },
|
||||
NoProvider,
|
||||
Denied { reason: &'static str },
|
||||
/// El holder ya tiene el máximo de tokens activos para esta cap.
|
||||
/// Debe esperar a que alguno expire o renovar uno existente.
|
||||
QuotaExceeded { active: u32, limit: u32 },
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! periódicamente con `renew_grant(token)`; en caso contrario, el background
|
||||
//! task `purge_expired_grants` los revoca al vencimiento.
|
||||
|
||||
use super::{ttl_for_capability, EnteGraph, GrantedCapability};
|
||||
use super::{quota_for_capability, ttl_for_capability, EnteGraph, GrantedCapability};
|
||||
use crate::events::CapabilityGrant;
|
||||
use ente_card::Capability;
|
||||
use std::time::Instant;
|
||||
@@ -22,24 +22,38 @@ impl EnteGraph {
|
||||
let grant = match self.providers.get(&cap).and_then(|s| s.iter().next().copied()) {
|
||||
None => CapabilityGrant::NoProvider,
|
||||
Some(provider) => {
|
||||
let token = self.next_token;
|
||||
self.next_token += 1;
|
||||
// TTL específico por variante de capability — caps escaladas
|
||||
// (Spawn, FilesystemRoot) viven menos.
|
||||
let ttl = ttl_for_capability(&cap);
|
||||
let expires_at = Instant::now() + ttl;
|
||||
self.grants.insert(token, GrantedCapability {
|
||||
cap: cap.clone(),
|
||||
provider,
|
||||
holder: from,
|
||||
expires_at,
|
||||
});
|
||||
CapabilityGrant::Granted { token }
|
||||
// Quota: contar tokens vivos para (from, cap). Si excede,
|
||||
// rechazar antes de emitir uno nuevo.
|
||||
let limit = quota_for_capability(&cap);
|
||||
let active = self.active_tokens_for(from, &cap);
|
||||
if active >= limit {
|
||||
CapabilityGrant::QuotaExceeded { active, limit }
|
||||
} else {
|
||||
let token = self.next_token;
|
||||
self.next_token += 1;
|
||||
let ttl = ttl_for_capability(&cap);
|
||||
let expires_at = Instant::now() + ttl;
|
||||
self.grants.insert(token, GrantedCapability {
|
||||
cap: cap.clone(),
|
||||
provider,
|
||||
holder: from,
|
||||
expires_at,
|
||||
});
|
||||
CapabilityGrant::Granted { token }
|
||||
}
|
||||
}
|
||||
};
|
||||
let _ = reply.send(grant);
|
||||
}
|
||||
|
||||
/// Cuenta tokens vivos (no expirados) emitidos a un holder para una cap.
|
||||
pub fn active_tokens_for(&self, holder: Ulid, cap: &Capability) -> u32 {
|
||||
let now = Instant::now();
|
||||
self.grants.values()
|
||||
.filter(|g| g.holder == holder && &g.cap == cap && g.expires_at > now)
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
/// Extiende un grant existente. Devuelve `true` si renovó. Si el token
|
||||
/// no existe o ya expiró, `false` (el cliente debe re-acquire).
|
||||
/// Usa el TTL específico de la cap del grant.
|
||||
|
||||
@@ -76,6 +76,24 @@ pub(in crate::graph) struct GrantedCapability {
|
||||
/// corto enough para que credenciales filtradas expiren rápidamente.
|
||||
pub const DEFAULT_GRANT_TTL: std::time::Duration = std::time::Duration::from_secs(60);
|
||||
|
||||
/// Quota máxima de tokens activos por (holder, cap). Caps escaladas tienen
|
||||
/// quota baja para limitar fugas de credenciales; caps de uso frecuente
|
||||
/// (Endpoint, Journal) son más laxas.
|
||||
pub fn quota_for_capability(cap: &Capability) -> u32 {
|
||||
match cap {
|
||||
// Caps escaladas: pocos tokens, fuerza patrón request-act-release.
|
||||
Capability::Spawn => 2,
|
||||
Capability::FilesystemRoot => 2,
|
||||
Capability::Device { .. } => 4,
|
||||
// Caps de propósito general.
|
||||
Capability::Endpoint { .. } => 16,
|
||||
Capability::KernelNetlink(_) => 4,
|
||||
Capability::LegacyLogind => 8,
|
||||
// Logging: hasta 32 streams.
|
||||
Capability::Journal => 32,
|
||||
}
|
||||
}
|
||||
|
||||
/// TTL específico por variante de Capability. Caps de mayor riesgo / costo
|
||||
/// (Spawn, FilesystemRoot) tienen TTL más corto; caps "logging" como
|
||||
/// Journal pueden vivir más.
|
||||
|
||||
@@ -342,7 +342,7 @@ async fn dispatch_graph_event(
|
||||
}
|
||||
// Snapshot del cerebro (observer state) en archivo adjunto
|
||||
let brain_path = path.with_extension("brain.json");
|
||||
let obs_snap = brain.observer.read().await.snapshot();
|
||||
let obs_snap = brain.observer.write().await.snapshot();
|
||||
match write_brain_snapshot(&brain_path, &obs_snap) {
|
||||
Ok(()) => info!(
|
||||
path = %brain_path.display(),
|
||||
|
||||
Reference in New Issue
Block a user