gioser
⎇ d6b8f18b43c9bd9bf7268e48b61392a2d62bb229 crates/ente-brain/schema/rule.k 159 líneas · 5.7 KB Historia crudo

Pausa: 11 crates del fractal Ente #0 con cerebro completo d6b8f18b4 · Sergio ·

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}