Pausa: 11 crates del fractal Ente #0 con cerebro completo
PID 1 boot + bus interno autenticado + cerebro KCL/Rust: - 6 lib crates de infra (card, bus, cas, kernel, soma, wasm, snapshot) - ente-brain: motor de reglas O(1), observer Shannon, cristalización, audit hash-chain, persistencia rules.k, Prometheus /metrics - KCL schemas card.k + rule.k como gramática autoritativa - compat-logind D-Bus, ente-echo demo provider, ente-zero PID 1 - 22 tests OK, ~3.8k LOC Rust + ~300 LOC KCL Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
//! brainctl: cliente CLI del introspect API.
|
||||
//!
|
||||
//! Uso:
|
||||
//! cargo run --example brainctl -p ente-brain -- list-rules
|
||||
//! cargo run --example brainctl -p ente-brain -- entropy
|
||||
//! cargo run --example brainctl -p ente-brain -- top 10
|
||||
//! cargo run --example brainctl -p ente-brain -- crystals
|
||||
//! cargo run --example brainctl -p ente-brain -- crystal-kcl 0
|
||||
//!
|
||||
//! Path del socket: $ENTE_BRAIN_SOCK o $XDG_RUNTIME_DIR/ente-brain.sock
|
||||
|
||||
use ente_brain::introspect::{call, IntrospectRequest, IntrospectResponse};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn socket_path() -> PathBuf {
|
||||
if let Ok(p) = std::env::var("ENTE_BRAIN_SOCK") {
|
||||
return p.into();
|
||||
}
|
||||
let runtime = std::env::var("XDG_RUNTIME_DIR")
|
||||
.unwrap_or_else(|_| std::env::var("TMPDIR").unwrap_or_else(|_| "/tmp".into()));
|
||||
format!("{runtime}/ente-brain.sock").into()
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let cmd = args.get(1).map(|s| s.as_str()).unwrap_or("entropy");
|
||||
|
||||
let req = match cmd {
|
||||
"list-rules" | "rules" => IntrospectRequest::ListRules,
|
||||
"entropy" => IntrospectRequest::EntropySnapshot,
|
||||
"top" => {
|
||||
let n: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(10);
|
||||
IntrospectRequest::TopCorrelations { n }
|
||||
}
|
||||
"crystals" => IntrospectRequest::Crystals,
|
||||
"crystal-kcl" => {
|
||||
let i: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
IntrospectRequest::CrystalKcl { index: i }
|
||||
}
|
||||
"promote" => {
|
||||
let i: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
IntrospectRequest::PromoteCrystal { index: i }
|
||||
}
|
||||
"remove" => {
|
||||
let id_s = args.get(2).ok_or_else(|| anyhow::anyhow!("se requiere <ulid>"))?;
|
||||
let id: ulid::Ulid = id_s.parse()?;
|
||||
IntrospectRequest::RemoveRule { id }
|
||||
}
|
||||
"audit" => {
|
||||
let limit: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(20);
|
||||
IntrospectRequest::ListAudit { limit }
|
||||
}
|
||||
other => {
|
||||
eprintln!("subcomando desconocido: {other}");
|
||||
eprintln!("válidos: list-rules | entropy | top <n> | crystals | crystal-kcl <i> | promote <i> | remove <ulid> | audit <limit>");
|
||||
std::process::exit(2);
|
||||
}
|
||||
};
|
||||
|
||||
let path = socket_path();
|
||||
let resp = call(&path, req).await?;
|
||||
print_response(&resp);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_response(r: &IntrospectResponse) {
|
||||
match r {
|
||||
IntrospectResponse::Rules(rs) => {
|
||||
println!("{} reglas vivas:", rs.len());
|
||||
for r in rs {
|
||||
println!(" {} prio={} kind={} actions={} wildcard={}",
|
||||
r.id, r.priority, r.event_kind_tag, r.action_count, r.scope_wildcard);
|
||||
}
|
||||
}
|
||||
IntrospectResponse::Rule(rule) => match rule {
|
||||
Some(r) => println!("{r:#?}"),
|
||||
None => println!("regla no encontrada"),
|
||||
},
|
||||
IntrospectResponse::Entropy { value_bits, sample_size, distinct_kinds, window_full } => {
|
||||
println!("Shannon entropy : {value_bits:.4} bits");
|
||||
println!("Sample size : {sample_size}");
|
||||
println!("Distinct kinds : {distinct_kinds}");
|
||||
println!("Window full : {window_full}");
|
||||
}
|
||||
IntrospectResponse::Correlations(entries) => {
|
||||
println!("{} pares (top, ordenado por co-ocurrencia):", entries.len());
|
||||
for e in entries {
|
||||
println!(" n={:>4} P(b|a)={:.3} PMI={:>6.3}b {} → {}",
|
||||
e.joint_count, e.conditional_prob, e.pmi_bits, e.a, e.b);
|
||||
}
|
||||
}
|
||||
IntrospectResponse::Crystals(cs) => {
|
||||
println!("{} cristales detectados:", cs.len());
|
||||
for (i, c) in cs.iter().enumerate() {
|
||||
println!(" [{i}] {:?} → {:?} P={:.3} PMI={:.3}b n={}",
|
||||
c.antecedent, c.consequent, c.conditional_prob, c.pmi, c.support);
|
||||
}
|
||||
}
|
||||
IntrospectResponse::Kcl(s) => println!("{s}"),
|
||||
IntrospectResponse::Promoted { rule_id, kcl_snippet } => {
|
||||
println!("regla creada: {rule_id}");
|
||||
println!("--- KCL para auditoría / persistencia ---");
|
||||
println!("{kcl_snippet}");
|
||||
}
|
||||
IntrospectResponse::Removed(was_present) => {
|
||||
if *was_present { println!("regla eliminada"); }
|
||||
else { println!("regla no encontrada"); }
|
||||
}
|
||||
IntrospectResponse::AuditEntries(entries) => {
|
||||
println!("{} entries de audit log:", entries.len());
|
||||
for e in entries {
|
||||
let prev = e.prev_sha.map(hex_short).unwrap_or_else(|| "—".into());
|
||||
let sha = hex_short(e.sha);
|
||||
println!(" seq={:>4} t={} prev={} sha={} {:?}",
|
||||
e.seq, e.timestamp_ms, prev, sha, e.action);
|
||||
}
|
||||
}
|
||||
IntrospectResponse::Error(e) => eprintln!("error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_short(sha: [u8; 32]) -> String {
|
||||
sha[..4].iter().map(|b| format!("{:02x}", b)).collect::<String>() + ".."
|
||||
}
|
||||
Reference in New Issue
Block a user