refactor(brain): A2 — split arje-brain en 3 sub-crates

DAG de dependencias limpio (modularidad horizontal):
- arje-brain-rules     — rules + engine + dispatch (motor determinista)
- arje-brain-cognitive — observer + crystallize (estadística)
- arje-brain-audit     — audit chain → CAS (accountability)
- arje-brain           — umbrella de integración (introspect +
                         autopromote + metrics + loader)

Habilitador clave: TimedEvent movido de observer.rs a rules.rs
(engine lo necesitaba, era el único acoplo que rompía el DAG).

arje-brain re-exporta la API de los 3 sub-crates: arje-zero y chasqui
(consumidores) no requieren cambios. cargo check --workspace verde.
24 tests del brain pasan (4 rules + 6 cognitive + 5 audit + 9 umbrella).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-20 00:24:48 +00:00
parent b83d40a833
commit 848fc7a072
21 changed files with 221 additions and 89 deletions
Generated
+40
View File
@@ -299,6 +299,9 @@ name = "arje-brain"
version = "0.0.1"
dependencies = [
"anyhow",
"arje-brain-audit",
"arje-brain-cognitive",
"arje-brain-rules",
"arje-card",
"arje-cas",
"base64 0.22.1",
@@ -311,6 +314,43 @@ dependencies = [
"ulid",
]
[[package]]
name = "arje-brain-audit"
version = "0.0.1"
dependencies = [
"anyhow",
"arje-brain-cognitive",
"arje-brain-rules",
"arje-cas",
"serde",
"serde_json",
"tokio",
"ulid",
]
[[package]]
name = "arje-brain-cognitive"
version = "0.0.1"
dependencies = [
"arje-brain-rules",
"serde",
"serde_json",
"ulid",
]
[[package]]
name = "arje-brain-rules"
version = "0.0.1"
dependencies = [
"anyhow",
"arje-card",
"base64 0.22.1",
"serde",
"serde_json",
"tracing",
"ulid",
]
[[package]]
name = "arje-bus"
version = "0.0.1"
+3
View File
@@ -29,6 +29,9 @@ members = [
"crates/runtime/arje-bus",
"crates/runtime/arje-cas",
"crates/runtime/arje-wasm",
"crates/runtime/arje-brain-rules",
"crates/runtime/arje-brain-cognitive",
"crates/runtime/arje-brain-audit",
"crates/runtime/arje-brain",
"crates/runtime/arje-echo",
+13 -9
View File
@@ -7,20 +7,24 @@ rule engine + audit log, y un ente de smoke test.
## Crates
| crate | tipo | rol |
| ------------- | ---- | ----------------------------------------------------------- |
| `arje-bus` | lib | Unix SOCK_STREAM + postcard framing. Announce/Invoke/List |
| `arje-cas` | lib | Content-addressed storage SHA-256: blobs Wasm + audit log |
| `arje-wasm` | lib | Encarna `Payload::Wasm` vía `wasmi` en thread dedicado |
| `arje-brain` | lib | Rule engine + observer estadístico + audit chain con CAS |
| ---------------------- | ---- | -------------------------------------------------- |
| `arje-bus` | lib | Unix SOCK_STREAM + postcard framing |
| `arje-cas` | lib | Content-addressed storage SHA-256: blobs + audit |
| `arje-wasm` | lib | Encarna `Payload::Wasm` vía `wasmi` |
| `arje-brain-rules` | lib | Motor determinista: rules + engine O(1) + dispatch |
| `arje-brain-cognitive` | lib | Observer estadístico + crystallize de patrones |
| `arje-brain-audit` | lib | Audit chain con hashes anclados al CAS |
| `arje-brain` | lib | Integración: introspect + autopromote + metrics |
| `arje-echo` | bin | Ente prueba — provee `Capability::Endpoint(echo)` |
## Dependencias
- `arje-bus``tokio` + `postcard`. Consumido por `init/arje-zero`.
- `arje-cas``sha2` + `sled`. Consumido por `arje-brain` (audit log)
y `arje-wasm` (blobs).
- `arje-brain``arje-bus`, `arje-cas`. Consumido por Init para
observabilidad estadística + reglas declarativas.
- `arje-cas``sha2` + `sled`. Consumido por `arje-brain-audit` y `arje-wasm`.
- **Brain split (DAG limpio)**: `arje-brain-rules` (base) ← `arje-brain-cognitive`
`arje-brain-audit``arje-brain` (umbrella de integración).
- `arje-brain` re-exporta la API de los 3 sub-crates para los
consumidores históricos (`arje-zero`, `chasqui`).
## Invariantes
@@ -0,0 +1,17 @@
[package]
name = "arje-brain-audit"
version = "0.0.1"
edition.workspace = true
license.workspace = true
publish.workspace = true
description = "Capa de accountability del brain: audit log con hash chain anclado al CAS."
[dependencies]
arje-brain-rules = { path = "../arje-brain-rules" }
arje-brain-cognitive = { path = "../arje-brain-cognitive" }
arje-cas = { path = "../arje-cas" }
serde = { workspace = true }
serde_json = { workspace = true }
ulid = { workspace = true }
tokio = { workspace = true }
anyhow = { workspace = true }
@@ -6,7 +6,7 @@
//! escribe al content-addressable store y devuelve el SHA del head, que
//! puede guardarse en un archivo de "head pointer" (fuera de scope aquí).
use crate::crystallize::Crystal;
use arje_brain_cognitive::crystallize::Crystal;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use ulid::Ulid;
@@ -323,7 +323,7 @@ pub fn collect_chain_from_cas(start_sha: [u8; 32]) -> anyhow::Result<Vec<AuditEn
/// log informativo (los archivos pueden no existir en el ambiente actual).
pub fn replay_chain(
start_sha: [u8; 32],
engine: &mut crate::engine::RuleEngine,
engine: &mut arje_brain_rules::engine::RuleEngine,
) -> ReplayReport {
let entries = match collect_chain_from_cas(start_sha) {
Ok(es) => es,
@@ -336,7 +336,7 @@ pub fn replay_chain(
for entry in &entries {
match &entry.action {
AuditAction::PromoteCrystal { rule_id, crystal } => {
let mut rule = crate::crystallize::crystal_to_rule(crystal);
let mut rule = arje_brain_cognitive::crystallize::crystal_to_rule(crystal);
rule.id = *rule_id; // preservar identidad histórica
engine.insert(rule);
}
@@ -422,7 +422,7 @@ mod tests {
// ---------- Tests de integración con CAS real (en directorio temporal) ----------
use crate::engine::RuleEngine;
use arje_brain_rules::engine::RuleEngine;
use std::sync::Mutex;
/// Lock para serializar tests que mutan ENTE_CAS_ROOT (test threads
@@ -459,7 +459,7 @@ mod tests {
}
}
use crate::rules::EventKind;
use arje_brain_rules::rules::EventKind;
#[test]
fn flush_round_trip_preserves_chain() {
@@ -0,0 +1,13 @@
//! arje-brain-audit — accountability del brain.
//!
//! Audit log con cadena de hashes anclada al content-addressed storage
//! (`arje-cas`). Permite verificar la integridad de la historia de
//! decisiones del brain y reconstruir el estado vía replay.
pub mod audit;
pub use audit::{
AuditAction, AuditEntry, AuditHeadPointer, AuditLog, ReplayReport,
VerificationReport, collect_chain_from_cas, reachable_from_head,
replay_chain, verify_chain_from_cas,
};
@@ -0,0 +1,13 @@
[package]
name = "arje-brain-cognitive"
version = "0.0.1"
edition.workspace = true
license.workspace = true
publish.workspace = true
description = "Capa cognitiva del brain: observer estadístico (entropía + información mutua) + cristalización de patrones en reglas."
[dependencies]
arje-brain-rules = { path = "../arje-brain-rules" }
serde = { workspace = true }
serde_json = { workspace = true }
ulid = { workspace = true }
@@ -10,7 +10,7 @@
//! resultante con serde — sin formatos intermedios.
use crate::observer::{GapStats, Observer};
use crate::rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope};
use arje_brain_rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope};
use serde::{Deserialize, Serialize};
use std::time::Instant;
use ulid::Ulid;
@@ -215,7 +215,7 @@ pub fn detect_pattern_crystals(obs: &Observer, params: &PatternParams) -> Vec<Pa
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::EventKind::*;
use arje_brain_rules::EventKind::*;
#[test]
fn detects_perfect_correlation() {
@@ -0,0 +1,11 @@
//! arje-brain-cognitive — capa estadística del brain.
//!
//! `Observer` con sliding window + marginales + co-ocurrencias + Shannon
//! entropy + información mutua. `crystallize` detecta patrones
//! estadísticamente significativos y los materializa como `Rule`.
pub mod observer;
pub mod crystallize;
pub use observer::Observer;
pub use crystallize::{detect_crystals, Crystal, CrystallizationParams};
@@ -8,18 +8,10 @@
//! - Sin recomputaciones globales: marginales y joint counts son state.
//! - El cálculo de H(X), P(B|A), I(A;B) es O(|distinct events|).
use crate::rules::EventKind;
use arje_brain_rules::{EventKind, TimedEvent};
use std::collections::{HashMap, VecDeque};
use std::time::Instant;
/// Evento timestamped. El timestamp se conserva para futuras políticas de
/// expiración por tiempo (no sólo por count).
#[derive(Debug, Clone)]
pub struct TimedEvent {
pub kind: EventKind,
pub at: Instant,
}
/// Histograma de gaps temporales con buckets exponenciales en segundos.
/// Cubre 6 órdenes de magnitud: 1ms hasta 1000s.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
@@ -420,7 +412,7 @@ pub struct ObserverSnapshot {
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::EventKind::*;
use arje_brain_rules::EventKind::*;
#[test]
fn entropy_zero_for_single_event() {
@@ -0,0 +1,16 @@
[package]
name = "arje-brain-rules"
version = "0.0.1"
edition.workspace = true
license.workspace = true
publish.workspace = true
description = "Motor de reglas determinista: triplets Subject+Event+Action, RuleEngine O(1), dispatch async."
[dependencies]
arje-card = { path = "../../protocol/arje-card" }
serde = { workspace = true }
serde_json = { workspace = true }
ulid = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }
base64 = { workspace = true }
@@ -5,7 +5,7 @@
//! Inmutabilidad fractal: `Arc<Rule>` es el unit de compartición. Clonar una
//! regla del motor para entregarla al dispatcher es un refcount bump, no copia.
use crate::observer::TimedEvent;
use crate::rules::TimedEvent;
use crate::rules::{EventKind, EventPattern, Rule, Scope};
use arje_card::Capability;
use std::collections::HashMap;
@@ -0,0 +1,13 @@
//! arje-brain-rules — motor de reglas determinista.
//!
//! Capa base del brain: tipos de regla (triplet Subject+Event+Action),
//! `RuleEngine` con dispatch O(1) por discriminante de evento, y el
//! ejecutor async de acciones. Sin dependencias estadísticas ni de UI.
pub mod rules;
pub mod engine;
pub mod dispatch;
pub use rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope, TimedEvent};
pub use engine::{EventKindDiscriminant, RuleEngine, SubjectInfo};
pub use dispatch::{dispatch_actions, ActionSink, NullSink};
@@ -7,8 +7,18 @@
use arje_card::Capability;
use serde::{Deserialize, Serialize};
use std::time::Instant;
use ulid::Ulid;
/// Evento timestamped. El timestamp se conserva para futuras políticas de
/// expiración por tiempo (no sólo por count). Tipo base compartido entre
/// el motor de reglas (`engine`) y el observador estadístico (`cognitive`).
#[derive(Debug, Clone)]
pub struct TimedEvent {
pub kind: EventKind,
pub at: Instant,
}
/// Triplet [Sujeto + Evento + Acción]. Inmutable tras carga.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Rule {
+4
View File
@@ -4,8 +4,12 @@ version = "0.0.1"
edition.workspace = true
license.workspace = true
publish.workspace = true
description = "Capa de integración del brain: introspect socket + autopromote loop + metrics HTTP + loader. Wirea arje-brain-{rules,cognitive,audit}."
[dependencies]
arje-brain-rules = { path = "../arje-brain-rules" }
arje-brain-cognitive = { path = "../arje-brain-cognitive" }
arje-brain-audit = { path = "../arje-brain-audit" }
arje-card = { path = "../../protocol/arje-card" }
arje-cas = { path = "../arje-cas" }
serde = { workspace = true }
+3 -3
View File
@@ -6,10 +6,10 @@
//! no exista ya una regla con el mismo trigger_kind (heurística simple —
//! evita ráfagas de duplicados de la misma estadística).
use crate::audit::AuditAction;
use crate::crystallize::{crystal_to_rule, detect_crystals, Crystal, CrystallizationParams};
use arje_brain_audit::audit::AuditAction;
use arje_brain_cognitive::crystallize::{crystal_to_rule, detect_crystals, Crystal, CrystallizationParams};
use crate::introspect::{append_rule_jsonl, BrainState};
use crate::rules::EventKind;
use arje_brain_rules::rules::EventKind;
use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;
+23 -23
View File
@@ -4,10 +4,10 @@
//! cerebro sin tocar el bus interno del fractal. Esto separa observación de
//! ejecución — la introspección es read-only por diseño.
use crate::crystallize::{detect_crystals, Crystal, CrystallizationParams};
use crate::engine::RuleEngine;
use crate::observer::Observer;
use crate::rules::Rule;
use arje_brain_cognitive::crystallize::{detect_crystals, Crystal, CrystallizationParams};
use arje_brain_rules::engine::RuleEngine;
use arje_brain_cognitive::observer::Observer;
use arje_brain_rules::rules::Rule;
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::path::{Path, PathBuf};
@@ -32,7 +32,7 @@ pub struct BrainState {
/// cada PromoteCrystal añade una línea (append-only) con la Rule serializada.
pub rules_out: Option<Arc<PathBuf>>,
/// Audit log en memoria. Cada promote/remove deja huella aquí.
pub audit: Arc<RwLock<crate::audit::AuditLog>>,
pub audit: Arc<RwLock<arje_brain_audit::audit::AuditLog>>,
}
impl BrainState {
@@ -46,7 +46,7 @@ impl BrainState {
observer: Arc::new(RwLock::new(Observer::new(window_size))),
params,
rules_out: None,
audit: Arc::new(RwLock::new(crate::audit::AuditLog::new())),
audit: Arc::new(RwLock::new(arje_brain_audit::audit::AuditLog::new())),
}
}
@@ -135,21 +135,21 @@ pub enum IntrospectResponse {
/// Resultado de RemoveRule: true si existía, false si ya no.
Removed(bool),
/// Entradas del audit log (más recientes al final).
AuditEntries(Vec<crate::audit::AuditEntry>),
AuditEntries(Vec<arje_brain_audit::audit::AuditEntry>),
/// Resultado de FlushAudit: cuántas entries se escribieron y SHA del head.
Flushed { written: usize, head_sha: Option<[u8; 32]>, total_flushed: u64 },
/// Resultado de ReloadRules: número total de reglas tras el reload.
Reloaded { count: usize },
/// Resultado de VerifyAudit.
AuditVerified(crate::audit::VerificationReport),
AuditVerified(arje_brain_audit::audit::VerificationReport),
/// Resultado de ReplayAudit.
Replayed(crate::audit::ReplayReport),
Replayed(arje_brain_audit::audit::ReplayReport),
/// Frame de streaming. El cliente lee estos en bucle hasta EOF.
AuditStreamFrame(crate::audit::AuditEntry),
AuditStreamFrame(arje_brain_audit::audit::AuditEntry),
/// Resultado de GcCas: cuántos blobs eliminados y bytes liberados.
GcResult { deleted: usize, freed_bytes: u64 },
/// Cristales de Burst/Silence detectados.
Patterns(Vec<crate::crystallize::PatternCrystal>),
Patterns(Vec<arje_brain_cognitive::crystallize::PatternCrystal>),
Error(String),
}
@@ -306,7 +306,7 @@ impl IntrospectServer {
let obs = self.state.observer.read().await;
let crystals = detect_crystals(&obs, &self.state.params);
match crystals.get(index) {
Some(c) => IntrospectResponse::Json(crate::crystallize::crystal_to_json_pretty(c)),
Some(c) => IntrospectResponse::Json(arje_brain_cognitive::crystallize::crystal_to_json_pretty(c)),
None => IntrospectResponse::Error(format!("no crystal at index {index}")),
}
}
@@ -317,7 +317,7 @@ impl IntrospectServer {
};
match crystals.get(index) {
Some(c) => {
let rule = crate::crystallize::crystal_to_rule(c);
let rule = arje_brain_cognitive::crystallize::crystal_to_rule(c);
let rule_id = rule.id;
let rule_json = serde_json::to_string_pretty(&rule)
.unwrap_or_else(|_| "<serialize failed>".into());
@@ -332,7 +332,7 @@ impl IntrospectServer {
}
// Audit entry
self.state.audit.write().await.append(
crate::audit::AuditAction::PromoteCrystal {
arje_brain_audit::audit::AuditAction::PromoteCrystal {
rule_id, crystal: c.clone(),
}
);
@@ -345,7 +345,7 @@ impl IntrospectServer {
let removed = self.state.engine.write().await.remove(id);
if removed {
self.state.audit.write().await.append(
crate::audit::AuditAction::RemoveRule { rule_id: id }
arje_brain_audit::audit::AuditAction::RemoveRule { rule_id: id }
);
}
IntrospectResponse::Removed(removed)
@@ -373,7 +373,7 @@ impl IntrospectServer {
"audit log sin entries flushadas — nada que verificar".into()
),
};
let report = crate::audit::verify_chain_from_cas(head);
let report = arje_brain_audit::audit::verify_chain_from_cas(head);
IntrospectResponse::AuditVerified(report)
}
IntrospectRequest::StreamAudit => {
@@ -385,15 +385,15 @@ impl IntrospectServer {
}
IntrospectRequest::PatternCrystals => {
let obs = self.state.observer.read().await;
let params = crate::crystallize::PatternParams::default();
let patterns = crate::crystallize::detect_pattern_crystals(&obs, &params);
let params = arje_brain_cognitive::crystallize::PatternParams::default();
let patterns = arje_brain_cognitive::crystallize::detect_pattern_crystals(&obs, &params);
IntrospectResponse::Patterns(patterns)
}
IntrospectRequest::GcCas { extra_roots } => {
// Reachable = audit chain desde head + extra_roots provistos.
let mut reachable = std::collections::HashSet::new();
if let Some(head) = self.state.audit.read().await.last_flushed_sha() {
reachable.extend(crate::audit::reachable_from_head(head));
reachable.extend(arje_brain_audit::audit::reachable_from_head(head));
}
reachable.extend(extra_roots);
match arje_cas::gc(&reachable) {
@@ -410,8 +410,8 @@ impl IntrospectServer {
),
};
let mut engine = self.state.engine.write().await;
*engine = crate::engine::RuleEngine::empty();
let report = crate::audit::replay_chain(head, &mut engine);
*engine = arje_brain_rules::engine::RuleEngine::empty();
let report = arje_brain_audit::audit::replay_chain(head, &mut engine);
IntrospectResponse::Replayed(report)
}
IntrospectRequest::ReloadRules { path } => {
@@ -430,12 +430,12 @@ impl IntrospectServer {
};
// Vaciamos el engine antes de re-cargar — semántica clean-slate.
let mut engine = self.state.engine.write().await;
*engine = crate::engine::RuleEngine::empty();
*engine = arje_brain_rules::engine::RuleEngine::empty();
let count = rules.len();
for r in rules { engine.insert(r); }
drop(engine);
self.state.audit.write().await.append(
crate::audit::AuditAction::LoadRulesFile {
arje_brain_audit::audit::AuditAction::LoadRulesFile {
path: path.to_string_lossy().into_owned(),
count,
}
+25 -29
View File
@@ -1,38 +1,34 @@
//! ente-brain: motor de reglas determinista + observador estadístico.
//! arje-brain — capa de integración del brain.
//!
//! Tres capas:
//! 1. `rules` — tipos de regla (Triplet: Subject + Event + Action)
//! 2. `engine` — RuleEngine con HashMap<EventKindDiscriminant, Vec<Arc<Rule>>>
//! para dispatch O(1)
//! 3. `dispatch` — ejecutor async de Actions (vía tokio)
//! 4. `observer` — sliding window + marginales + co-ocurrencias
//! + Shannon entropy + información mutua
//! 5. `crystallize` — detección de patrones estadísticamente significativos
//! y materialización en `Rule` ejecutables
//! 6. `introspect` — Unix socket bincode API para tools externos
//! El brain se divide en tres sub-crates con un DAG de dependencias limpio:
//! - `arje-brain-rules` — motor determinista (rules + engine + dispatch)
//! - `arje-brain-cognitive` — estadística (observer + crystallize)
//! - `arje-brain-audit` — accountability (audit chain → CAS)
//!
//! Diseño de inmutabilidad:
//! - Rules son `Arc<Rule>` — clonar es zero-copy (refcount bump).
//! - El motor expone sólo lecturas; mutaciones pasan por `insert/remove`.
//! - Observer mantiene contadores incrementales — sin recomputación.
//! Este crate es la capa que los wirea: `introspect` (socket API),
//! `autopromote` (loop de promoción de cristales), `metrics` (HTTP) y
//! `loader` (carga de cards/rules). Re-exporta la API de los tres
//! sub-crates para compatibilidad de los consumidores históricos.
pub mod audit;
pub mod autopromote;
pub mod crystallize;
pub mod dispatch;
pub mod engine;
pub mod introspect;
pub mod loader;
pub mod autopromote;
pub mod metrics;
pub mod observer;
pub mod rules;
pub mod loader;
// --- Re-export de los módulos de las 3 sub-crates ---
pub use arje_brain_rules::{dispatch, engine, rules};
pub use arje_brain_cognitive::{crystallize, observer};
pub use arje_brain_audit::audit;
// --- Re-exports planos (API histórica que consumen arje-zero, chasqui) ---
pub use rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope, TimedEvent};
pub use engine::{EventKindDiscriminant, RuleEngine, SubjectInfo};
pub use dispatch::{dispatch_actions, ActionSink, NullSink};
pub use crystallize::{detect_crystals, Crystal, CrystallizationParams};
pub use observer::Observer;
pub use audit::AuditLog;
pub use autopromote::{spawn_autopromote_loop, AutopromoteParams};
pub use crystallize::{detect_crystals, Crystal, CrystallizationParams};
pub use dispatch::{dispatch_actions, ActionSink, NullSink};
pub use engine::{EventKindDiscriminant, RuleEngine, SubjectInfo};
pub use introspect::{IntrospectRequest, IntrospectResponse, IntrospectServer, BrainState};
pub use introspect::{BrainState, IntrospectRequest, IntrospectResponse, IntrospectServer};
pub use loader::{load_card_file, load_rules_file};
pub use metrics::serve_metrics;
pub use observer::{Observer, TimedEvent};
pub use rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope};
+2 -2
View File
@@ -8,7 +8,7 @@
//! Ergonomía de autoría futura (RON, Dhall, etc.) se añade como ramas
//! adicionales aquí cuando duela escribir JSON a mano. Hoy: una sola rama.
use crate::rules::Rule;
use arje_brain_rules::rules::Rule;
use arje_card::EntityCard;
use std::path::Path;
use tracing::info;
@@ -102,7 +102,7 @@ pub fn extract_rules_from_json(raw: &str) -> anyhow::Result<Vec<Rule>> {
mod tests {
use super::*;
use crate::introspect::append_rule_jsonl;
use crate::rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope};
use arje_brain_rules::rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope};
use ulid::Ulid;
fn sample_rule() -> Rule {
+3 -3
View File
@@ -5,7 +5,7 @@
//! `prometheus` con su Registry + encoders.
use crate::introspect::BrainState;
use crate::rules::EventKind;
use arje_brain_rules::rules::EventKind;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
@@ -98,7 +98,7 @@ async fn format_metrics(state: &BrainState) -> String {
}
// ---- Cristales detectados (con params actuales) ----
let crystals = crate::detect_crystals(&obs, &state.params);
let crystals = arje_brain_cognitive::detect_crystals(&obs, &state.params);
out.push_str("# HELP ente_brain_crystals_total Number of crystals detected with current params.\n");
out.push_str("# TYPE ente_brain_crystals_total gauge\n");
out.push_str(&format!("ente_brain_crystals_total {}\n", crystals.len()));
@@ -135,7 +135,7 @@ async fn format_metrics(state: &BrainState) -> String {
// ---- Histogramas de gaps temporales (top-32 pares más frecuentes) ----
out.push_str("# HELP ente_brain_pair_gap_seconds Time gap between correlated events.\n");
out.push_str("# TYPE ente_brain_pair_gap_seconds histogram\n");
let limits = crate::observer::GapHistogram::bucket_limits();
let limits = arje_brain_cognitive::observer::GapHistogram::bucket_limits();
for ((a, b), hist) in obs.top_gap_pairs(32) {
let labels = format!(r#"a="{}",b="{}""#, kind_label(a), kind_label(b));
for (i, &limit) in limits.iter().enumerate() {