tawasuyu

tawasuyu/brahman

Rust 332TOML 127JSON 17Markdown 154710 Definitions

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 · by sergio · (5 months ago) · parent b83d40a83 · browse the tree at this commit

Semantic delta · 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/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 files · +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, &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,
                     }
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() {