d6b8f18b43
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>
179 lines
7.2 KiB
Plaintext
179 lines
7.2 KiB
Plaintext
# ============================================================================
|
|
# card.k — Genética del Ente. Esquema KCL para EntityCard.
|
|
#
|
|
# Esta es la gramática autoritativa: cualquier Card que se cargue al fractal
|
|
# debe pasar la validación de este esquema. El boot de PID 1 acepta JSON que
|
|
# cumple este shape (KCL exporta JSON tras validar `check:`).
|
|
#
|
|
# Para validar manualmente:
|
|
# kcl run examples/my-card.k --schema schema/card.k
|
|
#
|
|
# Cada `check:` es invariante de fractal. Romperlo = Card inválida = no boot.
|
|
# ============================================================================
|
|
|
|
# ---------- Identidad ----------
|
|
|
|
schema EntityCard:
|
|
"""Tarjeta de Identidad. Inmutable: cambios = nueva Card con nuevo id."""
|
|
schema_version: int = 1
|
|
id: str # Ulid (26 chars, Crockford base32)
|
|
lineage?: str # parent Ulid; None = Ente raíz
|
|
label: str # legible, no es identificador
|
|
provides: [Capability] = [] # contrato hacia el grafo
|
|
requires: [Capability] = [] # contrato del grafo hacia el Ente
|
|
soma: SomaSpec # cuerpo: aislamiento + recursos
|
|
payload: Payload # cómo encarnar (Wasm/Native/Virtual)
|
|
supervision: Supervision # política tras muerte
|
|
genesis?: [EntityCard] = [] # hijos a instanciar al encarnar
|
|
|
|
check:
|
|
schema_version == 1, "schema version no soportada"
|
|
len(label) > 0, "label vacío"
|
|
len(id) == 26, "id debe ser Ulid (26 caracteres)"
|
|
# Auto-dependencia: una capacidad no puede estar en requires y provides
|
|
all c in requires { c not in provides }, "self-dependency: ${c}"
|
|
|
|
|
|
# ---------- Capacidades (typed enum) ----------
|
|
|
|
# KCL no tiene sum types nativos; usamos tagged union: `kind` + campos opcionales
|
|
# que sólo aplican según el kind. Las invariantes en `check:` aseguran consistencia.
|
|
schema Capability:
|
|
"""Capacidad tipada del fractal. NUNCA usar strings libres."""
|
|
kind: "FilesystemRoot" | "KernelNetlink" | "Endpoint" | "LegacyLogind" | "Device" | "Spawn" | "Journal"
|
|
netlink_family?: "Uevent" | "Route" | "Generic" | "Audit"
|
|
endpoint_interface?: str # 32-char hex (UUID 16 bytes)
|
|
endpoint_version?: int
|
|
device_class?: "Block" | "Tty" | "Input" | "Drm" | "Net" | "Hidraw"
|
|
|
|
check:
|
|
kind != "KernelNetlink" or netlink_family is not None, \
|
|
"KernelNetlink requiere netlink_family"
|
|
kind != "Endpoint" or (endpoint_interface is not None and endpoint_version is not None), \
|
|
"Endpoint requiere interface + version"
|
|
kind != "Endpoint" or len(endpoint_interface) == 32, \
|
|
"endpoint_interface debe ser hex de 32 chars"
|
|
kind != "Device" or device_class is not None, \
|
|
"Device requiere device_class"
|
|
|
|
|
|
# ---------- Soma: cuerpo + restricciones de recursos ----------
|
|
|
|
schema SomaSpec:
|
|
"""Aislamiento + recursos. Validados por KCL antes de tocar el kernel."""
|
|
namespaces: NamespaceSet = NamespaceSet {}
|
|
rlimits: ResourceLimits = ResourceLimits {}
|
|
cgroup: CgroupSpec = CgroupSpec {}
|
|
cpu_affinity?: [int] # CPU pinning
|
|
|
|
check:
|
|
cpu_affinity is None or all c in cpu_affinity { c >= 0 and c < 1024 }, \
|
|
"cpu_affinity fuera de rango [0, 1024)"
|
|
|
|
|
|
schema NamespaceSet:
|
|
mount: bool = False
|
|
pid: bool = False
|
|
net: bool = False
|
|
uts: bool = False
|
|
ipc: bool = False
|
|
user: bool = False
|
|
cgroup: bool = False
|
|
|
|
|
|
schema ResourceLimits:
|
|
"""Restricciones nativas validadas en KCL — el kernel sólo ve valores sanos."""
|
|
mem_bytes?: int # RLIMIT_AS
|
|
nproc?: int # RLIMIT_NPROC
|
|
nofile?: int # RLIMIT_NOFILE
|
|
energy_budget_mw?: int # presupuesto energético (futuro)
|
|
|
|
check:
|
|
mem_bytes is None or mem_bytes > 0, "mem_bytes debe ser positivo"
|
|
mem_bytes is None or mem_bytes <= 1099511627776, "mem_bytes > 1 TiB sospechoso"
|
|
nproc is None or (nproc > 0 and nproc <= 65535), "nproc fuera de rango"
|
|
nofile is None or (nofile > 0 and nofile <= 1048576), "nofile fuera de rango"
|
|
energy_budget_mw is None or energy_budget_mw > 0, "energy_budget_mw debe ser positivo"
|
|
|
|
|
|
schema CgroupSpec:
|
|
"""Cgroup v2: path + weights. cpu_weight 1..10000 según kernel docs."""
|
|
path: str = ""
|
|
cpu_weight?: int
|
|
io_weight?: int
|
|
|
|
check:
|
|
cpu_weight is None or (cpu_weight >= 1 and cpu_weight <= 10000), \
|
|
"cpu_weight 1..10000"
|
|
io_weight is None or (io_weight >= 1 and io_weight <= 10000), \
|
|
"io_weight 1..10000"
|
|
|
|
|
|
# ---------- Payload: tagged union de cómo encarnar ----------
|
|
|
|
schema Payload:
|
|
"""Una variante por Card. Set exactly one of: Wasm, Native, Virtual, Legacy."""
|
|
kind: "Wasm" | "Native" | "Virtual" | "Legacy"
|
|
# Wasm
|
|
module_sha256?: str # hex 64 chars
|
|
entry?: str
|
|
# Native / Legacy
|
|
exec?: str
|
|
argv?: [str] = []
|
|
envp?: [{str: str}] = []
|
|
# Legacy
|
|
fakes?: ["SystemdLogind" | "SystemdHostnamed" | "SystemdNotify"] = []
|
|
|
|
check:
|
|
kind != "Wasm" or (module_sha256 is not None and entry is not None), \
|
|
"Wasm requiere module_sha256 + entry"
|
|
kind != "Wasm" or len(module_sha256) == 64, "module_sha256 debe ser hex de 64 chars"
|
|
kind != "Native" or exec is not None, "Native requiere exec"
|
|
kind != "Legacy" or exec is not None, "Legacy requiere exec"
|
|
|
|
|
|
# ---------- Supervision ----------
|
|
|
|
schema Supervision:
|
|
kind: "Restart" | "OneShot" | "Delegate"
|
|
initial_ms?: int # ms — backoff inicial para Restart
|
|
max_ms?: int # ms — backoff máximo
|
|
|
|
check:
|
|
kind != "Restart" or (initial_ms is not None and max_ms is not None), \
|
|
"Restart requiere initial_ms + max_ms"
|
|
initial_ms is None or initial_ms >= 0, "initial_ms negativo"
|
|
max_ms is None or max_ms >= initial_ms or max_ms is None, \
|
|
"max_ms < initial_ms es contradictorio"
|
|
|
|
|
|
# ============================================================================
|
|
# Herencia: EnteWeb hereda de EnteBase con campos pre-rellenados.
|
|
# ============================================================================
|
|
|
|
schema EnteBase(EntityCard):
|
|
"""Base para Entes managed: declara Spawn provider y Journal por defecto."""
|
|
schema_version = 1
|
|
supervision = Supervision {kind = "Restart", initial_ms = 100, max_ms = 30000}
|
|
soma = SomaSpec {
|
|
rlimits = ResourceLimits {nofile = 4096}
|
|
cgroup = CgroupSpec {path = "ente.slice/managed", cpu_weight = 100}
|
|
}
|
|
|
|
|
|
schema EnteWeb(EnteBase):
|
|
"""Hereda EnteBase, declara endpoint + cap LegacyLogind como ejemplo."""
|
|
provides = [
|
|
Capability {kind = "Journal"}
|
|
Capability {
|
|
kind = "Endpoint"
|
|
endpoint_interface = "deadbeefcafe1234deadbeefcafe1234"
|
|
endpoint_version = 1
|
|
}
|
|
]
|
|
soma = SomaSpec {
|
|
namespaces = NamespaceSet {net = True, mount = True, pid = True}
|
|
rlimits = ResourceLimits {nofile = 16384, mem_bytes = 536870912} # 512 MiB
|
|
cgroup = CgroupSpec {path = "ente.slice/web", cpu_weight = 200, io_weight = 100}
|
|
}
|