1# ============================================================================
2# rule.k — REFERENCE ONLY. NOT LOADED.
3#
4# La gramática autoritativa de Rule vive en Rust:
5# crates/ente-brain/src/rules.rs
6# El loader (crates/ente-brain/src/loader.rs) sólo acepta JSON / JSONL.
7#
8# Conservado como notas de diseño humano-legibles del shape Rule:
9# Triplet [Sujeto + Evento + Acción(Objeto)]. Cada regla es una sinapsis:
10# cuando ocurre `when`, el motor ejecuta `then` para los Entes que cumplen
11# `scope`. El motor las indexa por discriminante de EventKind para lookup
12# en O(1). Las reglas son inmutables tras carga (Arc<Rule>).
13#
14# Si cambias el shape en Rust, sincroniza este archivo a mano (o
15# reemplázalo por JSON Schema generado vía `schemars`).
16# ============================================================================
17
18schema Rule:
19 """Una sinapsis del fractal. Determinista, sin estado entre disparos."""
20 id: str # Ulid 26 chars
21 priority: int = 5 # 0..255, mayor = se ejecuta primero
22 when: EventPattern
23 then: [Action]
24 scope: Scope = Scope {} # qué Entes son sujetos válidos
25
26 check:
27 len(id) == 26, "id debe ser Ulid"
28 priority >= 0 and priority <= 255, "priority fuera de rango"
29 len(then) > 0, "regla sin acciones"
30
31
32# ---------- Subject: alcance del sujeto ----------
33
34schema Scope:
35 """Match del sujeto. None en todos los campos = match cualquier Ente."""
36 subject_id?: str # Ulid exacto
37 subject_label?: str # label exacto
38 subject_has_cap?: Capability # Ente que declara esta capacidad
39
40 check:
41 subject_id is None or len(subject_id) == 26, "subject_id no es Ulid"
42
43
44# ---------- Event: qué dispara la regla ----------
45
46# EventPattern es tagged union recursivo.
47#
48# Atómicos:
49# Single — match un evento por kind
50# Sequence — N eventos consecutivos dentro de within_ms
51#
52# Compuestos (recursivos):
53# Either — OR sobre sub-patterns
54# All — AND sobre sub-patterns (mismo event/history)
55schema EventPattern:
56 type: "Single" | "Sequence" | "Either" | "All"
57 kind?: EventKind # Single
58 kinds?: [EventKind] # Sequence
59 within_ms?: int = 0
60 patterns?: [EventPattern] # Either / All (recursivo)
61
62 check:
63 type != "Single" or kind is not None, "Single requiere kind"
64 type != "Sequence" or (kinds is not None and len(kinds) > 0), \
65 "Sequence requiere kinds no vacío"
66 type != "Either" or (patterns is not None and len(patterns) > 0), \
67 "Either requiere patterns no vacío"
68 type != "All" or (patterns is not None and len(patterns) > 0), \
69 "All requiere patterns no vacío"
70 within_ms is None or within_ms >= 0, "within_ms negativo"
71
72
73# EventKind con tag interno + payload opcional según tag.
74schema EventKind:
75 tag: "EnteSpawned" | "EnteDied" | "BusAnnounce" | "BusInvoke" | "BusInvokeOf" | "DeviceAdded" | "DeviceRemoved" | "Custom"
76 cap?: Capability # para BusInvokeOf
77 custom?: str # para Custom
78
79 check:
80 tag != "BusInvokeOf" or cap is not None, "BusInvokeOf requiere cap"
81 tag != "Custom" or custom is not None, "Custom requiere custom string"
82
83
84# ---------- Action: qué hacer ----------
85
86schema Action:
87 """Una acción ejecutable por el motor. Tagged union con kind."""
88 kind: "Log" | "Notify" | "Spawn" | "Invoke" | "Inhibit"
89 # Log
90 level?: "trace" | "debug" | "info" | "warn" | "error"
91 message?: str
92 # Notify
93 target_id?: str # Ulid
94 # Spawn
95 card_blob?: str # base64-encoded EntityCard JSON
96 # Invoke
97 target_cap?: Capability
98 blob_b64?: str
99 # Inhibit
100 reason?: str
101
102 check:
103 kind != "Log" or message is not None, "Log requiere message"
104 kind != "Notify" or (target_id is not None and message is not None), \
105 "Notify requiere target_id + message"
106 kind != "Spawn" or card_blob is not None, "Spawn requiere card_blob"
107 kind != "Invoke" or target_cap is not None, "Invoke requiere target_cap"
108 kind != "Inhibit" or reason is not None, "Inhibit requiere reason"
109
110
111# ---------- Capability: re-export desde card.k para evitar inclusión circular ----------
112
113# En uso real: `import ..ente_card.schema.card` y referencia Capability.
114# Aquí declaramos una versión alineada para auto-contención del esquema.
115schema Capability:
116 kind: "FilesystemRoot" | "KernelNetlink" | "Endpoint" | "LegacyLogind" | "Device" | "Spawn" | "Journal"
117 netlink_family?: "Uevent" | "Route" | "Generic" | "Audit"
118 endpoint_interface?: str
119 endpoint_version?: int
120 device_class?: "Block" | "Tty" | "Input" | "Drm" | "Net" | "Hidraw"
121
122
123# ============================================================================
124# Ejemplo de regla cristalizada (auto-generada por el observador)
125# ============================================================================
126
127example_rule = Rule {
128 id = "01KQQ100000000000000000000"
129 priority = 5
130 when = EventPattern {
131 type = "Single"
132 kind = EventKind {tag = "EnteSpawned"}
133 }
134 scope = Scope {
135 subject_label = "demo-echo"
136 }
137 then = [
138 Action {
139 kind = "Log"
140 level = "info"
141 message = "demo-echo encarnado, observando para crystallization"
142 }
143 ]
144}
145
146# Ejemplo de regla compuesta: cuando un Ente se anuncia y luego es invocado
147# en menos de 500ms, log estructurado para auditoría.
148example_sequence = Rule {
149 id = "01KQQ200000000000000000000"
150 priority = 7
151 when = EventPattern {
152 type = "Sequence"
153 kinds = [
154 EventKind {tag = "BusAnnounce"}
155 EventKind {tag = "BusInvoke"}
156 ]
157 within_ms = 500
158 }
159 scope = Scope {}
160 then = [
161 Action {
162 kind = "Log"
163 level = "info"
164 message = "patrón Announce→Invoke detectado <500ms"
165 }
166 ]
167}