168 lines
6.0 KiB
Plaintext
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"
|
|
}
|
|
]
|
|
}
|