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