refactor(nakui-core): KCL → Nickel — kcl_wrapper reemplazado por evaluación in-process
Cierra el plan original. El motor de validación de entities deja de shellear el binario externo `kcl` y pasa a evaluar Nickel contracts in-process via la dep nickel-lang (la misma que usa el brazo de cards). Los 3 schemas de sales/inventory/treasury migran de .k a .ncl. nakui-core: - Nueva dep nickel-lang = "2.0.0". - Borrado kcl_wrapper.rs. - Nuevo nickel_validator.rs con vet(schema_path, state, entity) que evalúa `let bundle = (import "<schema>") in (std.deserialize 'Json m%%"<json>"%%) | bundle.<entity>`. - executor.rs: KclError → NickelError, KclPre/Post/PostCreate → SchemaPre/Post/PostCreate, kcl_check → validate_entity. build_schema_bundle ahora emite `(import "X") & (import "Y") & ...` en lugar de concatenar bytes (cada .ncl es expresión completa). - manifest.rs: default schema "schema.ncl", extract_schema_names reescrito para sintaxis Nickel record (CapitalCase keys con 2-space indent). Schemas migrados: - sales/schema.ncl: Venta con std.contract.Sequence [record, from_predicate] para combinar shape + invariante cross-field (total == cantidad * precio_unitario). El patrón directo `record | from_predicate` rebota con "missing definition" porque el predicate evalúa antes de que el value populate el record; documentado en cada schema. - inventory/schema.ncl, treasury/schema.ncl: idem. - 3 schema.k viejos borrados; sales/nsmc.json paths actualizados. Tests: refs Kcl* renombradas; paths .k → .ncl; tests inline que escribían schema.k cambian a schema.ncl con sintaxis Nickel. 84 tests verdes en nakui-core. Doc-only borrados: - crates/core/ente-card/schema/card.k (REFERENCE ONLY). - crates/core/ente-brain/schema/rule.k (REFERENCE ONLY). Beneficios: sin dep externa al binario `kcl` (build CI limpio), errores Nickel en línea con caret pointing al field, mismo motor que cards (una dep para todo el repo), sin tempfile JSON intermedio. Cierra el plan original yahweh + KCL + card.k. Pendientes salen de nuevo trabajo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,167 +0,0 @@
|
||||
# ============================================================================
|
||||
# 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user