Files
arje/crates/ente-brain/schema/rule.k
T
Sergio d6b8f18b43 Pausa: 11 crates del fractal Ente #0 con cerebro completo
PID 1 boot + bus interno autenticado + cerebro KCL/Rust:
- 6 lib crates de infra (card, bus, cas, kernel, soma, wasm, snapshot)
- ente-brain: motor de reglas O(1), observer Shannon, cristalización,
  audit hash-chain, persistencia rules.k, Prometheus /metrics
- KCL schemas card.k + rule.k como gramática autoritativa
- compat-logind D-Bus, ente-echo demo provider, ente-zero PID 1
- 22 tests OK, ~3.8k LOC Rust + ~300 LOC KCL

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:57:44 +00:00

160 lines
5.7 KiB
Plaintext

# ============================================================================
# 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<Rule>).
# ============================================================================
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"
}
]
}