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:
Generated
+40
@@ -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"
|
||||
|
||||
@@ -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
@@ -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 }
|
||||
+5
-5
@@ -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 }
|
||||
+2
-2
@@ -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};
|
||||
+2
-10
@@ -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 }
|
||||
+1
-1
@@ -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,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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user