gioser

prueba

c6a131ac259b8809d1d65e463a9203e6493decb4 · ruwaq Sergio · (5 killa ñawpaq) · tayta ebfdf4317 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 7

crates/ente-brain/src/loader.rs

3 qillqakuna · +93 −12

diff --git a/crates/ente-brain/schema/rule.k b/crates/ente-brain/schema/rule.k
index f7fb468..432cde5 100644
--- a/crates/ente-brain/schema/rule.k
+++ b/crates/ente-brain/schema/rule.k
@@ -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:
diff --git a/crates/ente-brain/src/loader.rs b/crates/ente-brain/src/loader.rs
index 93a9a50..296ebc1 100644
--- a/crates/ente-brain/src/loader.rs
+++ b/crates/ente-brain/src/loader.rs
@@ -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
+    }
+}
diff --git a/crates/ente-card/schema/card.k b/crates/ente-card/schema/card.k
index bc92b35..1c1232b 100644
--- a/crates/ente-card/schema/card.k
+++ b/crates/ente-card/schema/card.k
@@ -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 ----------