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>
848fc7a0727d88eb9b45522acc083c95d480407f · por sergio · 2026-05-20T00:24:48Z (hace 5 meses) · padre b83d40a83 · ver el árbol en este commit
Delta semántico · 321
crates/runtime/arje-brain-audit/src/audit.rs
crates/runtime/arje-brain-audit/src/lib.rs
crates/runtime/arje-brain-cognitive/src/crystallize.rs
crates/runtime/arje-brain-cognitive/src/lib.rs
crates/runtime/arje-brain-cognitive/src/observer.rs
crates/runtime/arje-brain-rules/src/dispatch.rs
crates/runtime/arje-brain-rules/src/engine.rs
crates/runtime/arje-brain-rules/src/lib.rs
crates/runtime/arje-brain-rules/src/rules.rs
crates/runtime/arje-brain/src/audit.rs
crates/runtime/arje-brain/src/crystallize.rs
crates/runtime/arje-brain/src/dispatch.rs
crates/runtime/arje-brain/src/engine.rs
crates/runtime/arje-brain/src/introspect.rs
crates/runtime/arje-brain/src/lib.rs
crates/runtime/arje-brain/src/metrics.rs
crates/runtime/arje-brain/src/observer.rs
crates/runtime/arje-brain/src/rules.rs
27 archivos · +2128 −1996
diff --git a/Cargo.lock b/Cargo.lock
index 225933b..1a8bad8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 5fa01bf..4eca129 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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",
diff --git a/crates/runtime/SDD.md b/crates/runtime/SDD.md
index 1ac0f0a..06a2c7c 100644
--- a/crates/runtime/SDD.md
+++ b/crates/runtime/SDD.md
@@ -6,21 +6,25 @@ 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-echo` | bin | Ente prueba — provee `Capability::Endpoint(echo)` |
+| crate | tipo | rol |
+| ---------------------- | ---- | -------------------------------------------------- |
+| `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
diff --git a/crates/runtime/arje-brain-audit/Cargo.toml b/crates/runtime/arje-brain-audit/Cargo.toml
new file mode 100644
index 0000000..251a779
--- /dev/null
+++ b/crates/runtime/arje-brain-audit/Cargo.toml
@@ -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 }
diff --git a/crates/runtime/arje-brain/src/audit.rs b/crates/runtime/arje-brain-audit/src/audit.rs
similarity index 98%
rename from crates/runtime/arje-brain/src/audit.rs
rename to crates/runtime/arje-brain-audit/src/audit.rs
index 24a8ec0..27af464 100644
--- a/crates/runtime/arje-brain/src/audit.rs
+++ b/crates/runtime/arje-brain-audit/src/audit.rs
@@ -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() {
diff --git a/crates/runtime/arje-brain-audit/src/lib.rs b/crates/runtime/arje-brain-audit/src/lib.rs
new file mode 100644
index 0000000..fec5570
--- /dev/null
+++ b/crates/runtime/arje-brain-audit/src/lib.rs
@@ -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,
+};
diff --git a/crates/runtime/arje-brain-cognitive/Cargo.toml b/crates/runtime/arje-brain-cognitive/Cargo.toml
new file mode 100644
index 0000000..72456e5
--- /dev/null
+++ b/crates/runtime/arje-brain-cognitive/Cargo.toml
@@ -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 }
diff --git a/crates/runtime/arje-brain/src/crystallize.rs b/crates/runtime/arje-brain-cognitive/src/crystallize.rs
similarity index 98%
rename from crates/runtime/arje-brain/src/crystallize.rs
rename to crates/runtime/arje-brain-cognitive/src/crystallize.rs
index 6a90660..d7a84cc 100644
--- a/crates/runtime/arje-brain/src/crystallize.rs
+++ b/crates/runtime/arje-brain-cognitive/src/crystallize.rs
@@ -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() {
diff --git a/crates/runtime/arje-brain-cognitive/src/lib.rs b/crates/runtime/arje-brain-cognitive/src/lib.rs
new file mode 100644
index 0000000..b452b8f
--- /dev/null
+++ b/crates/runtime/arje-brain-cognitive/src/lib.rs
@@ -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};
diff --git a/crates/runtime/arje-brain/src/observer.rs b/crates/runtime/arje-brain-cognitive/src/observer.rs
similarity index 98%
rename from crates/runtime/arje-brain/src/observer.rs
rename to crates/runtime/arje-brain-cognitive/src/observer.rs
index 2dbf7b5..5a2642d 100644
--- a/crates/runtime/arje-brain/src/observer.rs
+++ b/crates/runtime/arje-brain-cognitive/src/observer.rs
@@ -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() {
diff --git a/crates/runtime/arje-brain-rules/Cargo.toml b/crates/runtime/arje-brain-rules/Cargo.toml
new file mode 100644
index 0000000..712dea2
--- /dev/null
+++ b/crates/runtime/arje-brain-rules/Cargo.toml
@@ -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 }
diff --git a/crates/runtime/arje-brain/src/dispatch.rs b/crates/runtime/arje-brain-rules/src/dispatch.rs
similarity index 100%
rename from crates/runtime/arje-brain/src/dispatch.rs
rename to crates/runtime/arje-brain-rules/src/dispatch.rs
diff --git a/crates/runtime/arje-brain/src/engine.rs b/crates/runtime/arje-brain-rules/src/engine.rs
similarity index 99%
rename from crates/runtime/arje-brain/src/engine.rs
rename to crates/runtime/arje-brain-rules/src/engine.rs
index c9363ca..c2af0e6 100644
--- a/crates/runtime/arje-brain/src/engine.rs
+++ b/crates/runtime/arje-brain-rules/src/engine.rs
@@ -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;
diff --git a/crates/runtime/arje-brain-rules/src/lib.rs b/crates/runtime/arje-brain-rules/src/lib.rs
new file mode 100644
index 0000000..bd9d698
--- /dev/null
+++ b/crates/runtime/arje-brain-rules/src/lib.rs
@@ -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};
diff --git a/crates/runtime/arje-brain/src/rules.rs b/crates/runtime/arje-brain-rules/src/rules.rs
similarity index 94%
rename from crates/runtime/arje-brain/src/rules.rs
rename to crates/runtime/arje-brain-rules/src/rules.rs
index c78fa2e..78b2272 100644
--- a/crates/runtime/arje-brain/src/rules.rs
+++ b/crates/runtime/arje-brain-rules/src/rules.rs
@@ -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 {
diff --git a/crates/runtime/arje-brain/Cargo.toml b/crates/runtime/arje-brain/Cargo.toml
index a7a6a6e..bb1032f 100644
--- a/crates/runtime/arje-brain/Cargo.toml
+++ b/crates/runtime/arje-brain/Cargo.toml
@@ -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 }
diff --git a/crates/runtime/arje-brain/src/autopromote.rs b/crates/runtime/arje-brain/src/autopromote.rs
index a54929e..341a042 100644
--- a/crates/runtime/arje-brain/src/autopromote.rs
+++ b/crates/runtime/arje-brain/src/autopromote.rs
@@ -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;
diff --git a/crates/runtime/arje-brain/src/introspect.rs b/crates/runtime/arje-brain/src/introspect.rs
index 8e4961e..8990cf9 100644
--- a/crates/runtime/arje-brain/src/introspect.rs
+++ b/crates/runtime/arje-brain/src/introspect.rs
@@ -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, ¶ms);
+ let params = arje_brain_cognitive::crystallize::PatternParams::default();
+ let patterns = arje_brain_cognitive::crystallize::detect_pattern_crystals(&obs, ¶ms);
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,
}
diff --git a/crates/runtime/arje-brain/src/lib.rs b/crates/runtime/arje-brain/src/lib.rs
index 49ac4eb..372eeaf 100644
--- a/crates/runtime/arje-brain/src/lib.rs
+++ b/crates/runtime/arje-brain/src/lib.rs
@@ -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;
-pub use autopromote::{spawn_autopromote_loop, AutopromoteParams};
-pub use crystallize::{detect_crystals, Crystal, CrystallizationParams};
-pub use dispatch::{dispatch_actions, ActionSink, NullSink};
+// --- 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 introspect::{IntrospectRequest, IntrospectResponse, IntrospectServer, BrainState};
+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 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};
diff --git a/crates/runtime/arje-brain/src/loader.rs b/crates/runtime/arje-brain/src/loader.rs
index 2ca34ad..fba74f4 100644
--- a/crates/runtime/arje-brain/src/loader.rs
+++ b/crates/runtime/arje-brain/src/loader.rs
@@ -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 {
diff --git a/crates/runtime/arje-brain/src/metrics.rs b/crates/runtime/arje-brain/src/metrics.rs
index 06b7c79..0c201d7 100644
--- a/crates/runtime/arje-brain/src/metrics.rs
+++ b/crates/runtime/arje-brain/src/metrics.rs
@@ -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() {