Files
arje/crates/ente-brain/schema/rule.k
T
2026-05-07 14:21:51 +00:00

168 lines
6.0 KiB
Plaintext

# ============================================================================
# rule.k — REFERENCE ONLY. NOT LOADED.
#
# 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:
"""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"
}
]
}