# ============================================================================ # 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`. # # El motor en Rust las indexa por discriminante de EventKind para lookup # en O(1). Las reglas son inmutables tras carga (Arc). # ============================================================================ schema Rule: """Una sinapsis del fractal. Determinista, sin estado entre disparos.""" id: str # Ulid 26 chars priority: int = 5 # 0..255, mayor = se ejecuta primero when: EventPattern then: [Action] scope: Scope = Scope {} # qué Entes son sujetos válidos check: len(id) == 26, "id debe ser Ulid" priority >= 0 and priority <= 255, "priority fuera de rango" len(then) > 0, "regla sin acciones" # ---------- Subject: alcance del sujeto ---------- schema Scope: """Match del sujeto. None en todos los campos = match cualquier Ente.""" subject_id?: str # Ulid exacto subject_label?: str # label exacto subject_has_cap?: Capability # Ente que declara esta capacidad check: subject_id is None or len(subject_id) == 26, "subject_id no es Ulid" # ---------- Event: qué dispara la regla ---------- # EventPattern es tagged union recursivo. # # Atómicos: # Single — match un evento por kind # Sequence — N eventos consecutivos dentro de within_ms # # Compuestos (recursivos): # Either — OR sobre sub-patterns # All — AND sobre sub-patterns (mismo event/history) schema EventPattern: type: "Single" | "Sequence" | "Either" | "All" kind?: EventKind # Single kinds?: [EventKind] # Sequence within_ms?: int = 0 patterns?: [EventPattern] # Either / All (recursivo) check: type != "Single" or kind is not None, "Single requiere kind" type != "Sequence" or (kinds is not None and len(kinds) > 0), \ "Sequence requiere kinds no vacío" type != "Either" or (patterns is not None and len(patterns) > 0), \ "Either requiere patterns no vacío" type != "All" or (patterns is not None and len(patterns) > 0), \ "All requiere patterns no vacío" within_ms is None or within_ms >= 0, "within_ms negativo" # EventKind con tag interno + payload opcional según tag. schema EventKind: tag: "EnteSpawned" | "EnteDied" | "BusAnnounce" | "BusInvoke" | "BusInvokeOf" | "DeviceAdded" | "DeviceRemoved" | "Custom" cap?: Capability # para BusInvokeOf custom?: str # para Custom check: tag != "BusInvokeOf" or cap is not None, "BusInvokeOf requiere cap" tag != "Custom" or custom is not None, "Custom requiere custom string" # ---------- Action: qué hacer ---------- schema Action: """Una acción ejecutable por el motor. Tagged union con kind.""" kind: "Log" | "Notify" | "Spawn" | "Invoke" | "Inhibit" # Log level?: "trace" | "debug" | "info" | "warn" | "error" message?: str # Notify target_id?: str # Ulid # Spawn card_blob?: str # base64-encoded EntityCard JSON # Invoke target_cap?: Capability blob_b64?: str # Inhibit reason?: str check: kind != "Log" or message is not None, "Log requiere message" kind != "Notify" or (target_id is not None and message is not None), \ "Notify requiere target_id + message" kind != "Spawn" or card_blob is not None, "Spawn requiere card_blob" kind != "Invoke" or target_cap is not None, "Invoke requiere target_cap" kind != "Inhibit" or reason is not None, "Inhibit requiere reason" # ---------- Capability: re-export desde card.k para evitar inclusión circular ---------- # En uso real: `import ..ente_card.schema.card` y referencia Capability. # Aquí declaramos una versión alineada para auto-contención del esquema. schema Capability: kind: "FilesystemRoot" | "KernelNetlink" | "Endpoint" | "LegacyLogind" | "Device" | "Spawn" | "Journal" netlink_family?: "Uevent" | "Route" | "Generic" | "Audit" endpoint_interface?: str endpoint_version?: int device_class?: "Block" | "Tty" | "Input" | "Drm" | "Net" | "Hidraw" # ============================================================================ # Ejemplo de regla cristalizada (auto-generada por el observador) # ============================================================================ example_rule = Rule { id = "01KQQ100000000000000000000" priority = 5 when = EventPattern { type = "Single" kind = EventKind {tag = "EnteSpawned"} } scope = Scope { subject_label = "demo-echo" } then = [ Action { kind = "Log" level = "info" message = "demo-echo encarnado, observando para crystallization" } ] } # Ejemplo de regla compuesta: cuando un Ente se anuncia y luego es invocado # en menos de 500ms, log estructurado para auditoría. example_sequence = Rule { id = "01KQQ200000000000000000000" priority = 7 when = EventPattern { type = "Sequence" kinds = [ EventKind {tag = "BusAnnounce"} EventKind {tag = "BusInvoke"} ] within_ms = 500 } scope = Scope {} then = [ Action { kind = "Log" level = "info" message = "patrón Announce→Invoke detectado <500ms" } ] }