This commit is contained in:
Sergio
2026-05-07 14:21:51 +00:00
parent ebfdf43170
commit c6a131ac25
3 changed files with 93 additions and 12 deletions
+12 -4
View File
@@ -1,10 +1,18 @@
# ============================================================================
# rule.k — Triplet [Sujeto + Evento + Acción(Objeto)]. La gramática del
# Cerebro del fractal. Cada regla es una sinapsis: cuando ocurre `when`,
# el motor ejecuta `then` para todos los Entes que cumplen `scope`.
# rule.k — REFERENCE ONLY. NOT LOADED.
#
# El motor en Rust las indexa por discriminante de EventKind para lookup
# La gramática autoritativa de Rule vive en Rust:
# crates/ente-brain/src/rules.rs
# El loader (crates/ente-brain/src/loader.rs) sólo acepta JSON / JSONL.
#
# Conservado como notas de diseño humano-legibles del shape Rule:
# Triplet [Sujeto + Evento + Acción(Objeto)]. Cada regla es una sinapsis:
# cuando ocurre `when`, el motor ejecuta `then` para los Entes que cumplen
# `scope`. El motor las indexa por discriminante de EventKind para lookup
# en O(1). Las reglas son inmutables tras carga (Arc<Rule>).
#
# Si cambias el shape en Rust, sincroniza este archivo a mano (o
# reemplázalo por JSON Schema generado vía `schemars`).
# ============================================================================
schema Rule:
+73
View File
@@ -97,3 +97,76 @@ pub fn extract_rules_from_json(raw: &str) -> anyhow::Result<Vec<Rule>> {
}
Ok(rules)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::introspect::append_rule_jsonl;
use crate::rules::{Action, EventKind, EventPattern, LogLevel, Rule, Scope};
use ulid::Ulid;
fn sample_rule() -> Rule {
Rule {
id: Ulid::new(),
priority: 5,
when: EventPattern::Single { kind: EventKind::EnteSpawned },
then: vec![Action::Log {
level: LogLevel::Info,
message: "test".into(),
}],
scope: Scope::default(),
}
}
#[test]
fn rules_from_array() {
let r = sample_rule();
let raw = format!("[{}]", serde_json::to_string(&r).unwrap());
let parsed = extract_rules_from_json(&raw).expect("array parse");
assert_eq!(parsed.len(), 1);
}
#[test]
fn rules_from_object_with_array() {
let r = sample_rule();
let raw = format!(r#"{{"rules":[{}]}}"#, serde_json::to_string(&r).unwrap());
let parsed = extract_rules_from_json(&raw).expect("object parse");
assert_eq!(parsed.len(), 1);
}
#[test]
fn rules_from_jsonl_with_comments_and_blanks() {
let r1 = sample_rule();
let r2 = sample_rule();
let raw = format!(
"# header comment\n\n{}\n# inline comment\n{}\n\n",
serde_json::to_string(&r1).unwrap(),
serde_json::to_string(&r2).unwrap()
);
let parsed = extract_rules_from_json(&raw).expect("jsonl parse");
assert_eq!(parsed.len(), 2);
}
#[test]
fn append_rule_jsonl_roundtrip() {
let dir = tempdir_unique();
let path = dir.join("rules.jsonl");
let r1 = sample_rule();
let r2 = sample_rule();
append_rule_jsonl(&path, &r1).expect("append 1");
append_rule_jsonl(&path, &r2).expect("append 2");
let raw = std::fs::read_to_string(&path).expect("read back");
let parsed = extract_rules_from_json(&raw).expect("roundtrip parse");
assert_eq!(parsed.len(), 2);
assert_eq!(parsed[0].id, r1.id);
assert_eq!(parsed[1].id, r2.id);
let _ = std::fs::remove_dir_all(&dir);
}
fn tempdir_unique() -> std::path::PathBuf {
let base = std::env::temp_dir();
let p = base.join(format!("ente-brain-loader-{}", Ulid::new()));
std::fs::create_dir_all(&p).unwrap();
p
}
}
+8 -8
View File
@@ -1,14 +1,14 @@
# ============================================================================
# card.k — Genética del Ente. Esquema KCL para EntityCard.
# card.k — REFERENCE ONLY. NOT LOADED.
#
# Esta es la gramática autoritativa: cualquier Card que se cargue al fractal
# debe pasar la validación de este esquema. El boot de PID 1 acepta JSON que
# cumple este shape (KCL exporta JSON tras validar `check:`).
# La validación canónica de EntityCard vive en Rust:
# crates/ente-card/src/lib.rs :: EntityCard::validate()
# El loader (crates/ente-brain/src/loader.rs) sólo acepta JSON.
#
# Para validar manualmente:
# kcl run examples/my-card.k --schema schema/card.k
#
# Cada `check:` es invariante de fractal. Romperlo = Card inválida = no boot.
# Este archivo se conserva como notas de diseño legibles para humanos sobre
# las invariantes que `validate()` debe garantizar. Si modificas el shape
# en Rust, sincroniza este archivo a mano (o reemplázalo por JSON Schema
# generado vía `schemars`).
# ============================================================================
# ---------- Identidad ----------