tawasuyu

tawasuyu/brahman

Rust 483TOML 191Markdown 50JSON 176777 Definiciones

docs(arje): organiza core/ + seeds canónicas + boot reproducible

- crates/core/README.md: agrupamiento lógico de los 31 crates absorbidos
  de arje (ente-*) y del protocolo brahman (brahman-*) en 6 grupos —
  Init/PID 1, contratos, discovery, IPC+CAS, cerebro, 14 shims compat
  systemd. No se movieron crates físicamente (rompería paths
  cross-workspace).

- seeds/arje-minimal.card.json: PID1 + /bin/sh, smoke test QEMU.
- seeds/arje-prod.card.json: PID1 + 14 shims compat + tmpfiles/binfmt
  one-shots + echo + getty (16 children). Validados con
  brahman_card::Card::validate.
- seeds/validate.sh: carga la seed vía ente-zero en dev mode.

- scripts/build-arje-initrd.sh: empaqueta CPIO+gzip newc layout
  /init→/sbin/ente-zero, /usr/sbin/ente-*-compat, /ente/seed.card.json,
  /bin/{sh,...} (busybox o glibc+ldd). Tested: produce 20 MB initrd OK.
- scripts/run-arje-qemu.sh: qemu-system-x86_64 con KVM auto-detect,
  -kernel/-initrd/-append "rdinit=/init console=ttyS0,115200 panic=10".

- docs/arje-boot.md: doc end-to-end — layout initramfs, QEMU (con kernel
  del host o externo), GRUB bare metal, Proxmox/libvirt args:, schema
  de Card con todas las validaciones, debugging (sockets de
  introspección, snapshot/restore, metrics), checklist pre-deploy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

8592bab19ee819aef527a3654676676c97aa679d · por sergio · (hace 5 meses) · padre a92fa1577 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

8 archivos · +1046 −0

diff --git a/.gitignore b/.gitignore
index b86de51..42b39a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
 Cargo.lock.bak
 .DS_Store
 .claude/
+/out/
diff --git a/crates/core/README.md b/crates/core/README.md
new file mode 100644
index 0000000..652fcad
--- /dev/null
+++ b/crates/core/README.md
@@ -0,0 +1,100 @@
+# `crates/core/` — Init Arje (absorbido) + Protocolo Brahman
+
+El directorio agrupa **dos linajes** que se fusionaron al absorberse arje
+dentro del workspace de brahman:
+
+| linaje      | prefijo      | función                                          |
+| ----------- | ------------ | ------------------------------------------------ |
+| `arje`      | `ente-*`     | Init (PID 1), encarnación Linux, compat systemd  |
+| `brahman`   | `brahman-*`  | Tarjeta canónica, handshake, broker, admin       |
+
+No están en sub-carpetas físicas porque el workspace declara los paths uno
+a uno en `Cargo.toml` raíz y muchos `Cargo.toml` hijos usan `path =
+"../ente-X"`. El agrupamiento siguiente es **lógico**: cada crate se
+encuentra como `crates/core/<nombre>`.
+
+---
+
+## 1. Init / PID 1
+
+| crate           | tipo       | resumen                                                            |
+| --------------- | ---------- | ------------------------------------------------------------------ |
+| `ente-zero`     | binario    | PID 1 del fractal. Bucle primordial (reap + bus + handshake).      |
+| `ente-kernel`   | lib        | `bootstrap_kernel_surface()`, subreaper, SIGCHLD/uevent streams.   |
+| `ente-soma`     | lib (shim) | Re-export sobre `crates/shared/ente-incarnate` (clone+ns+cgroup).  |
+| `ente-snapshot` | lib        | `FractalSnapshot` JSON — checkpoint/restore del grafo de Cards.    |
+
+## 2. Contratos canónicos
+
+| crate              | resumen                                                              |
+| ------------------ | -------------------------------------------------------------------- |
+| `brahman-card`     | `Card { soma, payload, flow, permissions, supervision, genesis }`.   |
+| `brahman-card-wit` | Extracción de interfaces WIT de componentes WASM.                    |
+| `brahman-cards`    | Helpers para construir Cards típicas (consumer/producer/broker).     |
+| `ente-card`        | Alias histórico — re-export de `brahman-card` con nombres legacy.    |
+
+## 3. Discovery / Routing
+
+| crate                | resumen                                                            |
+| -------------------- | ------------------------------------------------------------------ |
+| `brahman-handshake`  | Protocolo Init↔módulo (Hello, Ping, ListSessions) postcard/Unix.   |
+| `brahman-broker`     | Service locator: empareja `flow.input` ↔ `flow.output` por tipo.   |
+| `brahman-admin`      | Socket separado para snapshots de sesiones + matches.              |
+
+## 4. IPC interno + Storage
+
+| crate         | resumen                                                                 |
+| ------------- | ----------------------------------------------------------------------- |
+| `ente-bus`    | Unix SOCK_STREAM con framing postcard. `Announce`/`Invoke`/`ListEntes`. |
+| `ente-cas`    | Content-addressed storage SHA-256 (blobs Wasm, audit log).              |
+| `ente-wasm`   | Encarna `Payload::Wasm` vía `wasmi` en thread dedicado.                 |
+
+## 5. Cerebro / Observabilidad
+
+| crate         | resumen                                                                  |
+| ------------- | ------------------------------------------------------------------------ |
+| `ente-brain`  | Rule engine + observer estadístico + audit log con hash chain a CAS.     |
+| `ente-echo`   | Ente de prueba — provee `Capability::Endpoint(echo)` para smoke tests.   |
+
+## 6. Compat systemd (shims D-Bus)
+
+Cada shim es un binario que se anuncia con un nombre well-known
+`org.freedesktop.X1` y traduce las llamadas al bus interno. Esto permite
+que GNOME/KDE/aplicaciones legacy arranquen sobre arje sin systemd:
+
+| binario                     | reemplaza             | nombre D-Bus                        |
+| --------------------------- | --------------------- | ----------------------------------- |
+| `ente-logind-compat`        | `systemd-logind`      | `org.freedesktop.login1`            |
+| `ente-hostnamed-compat`     | `systemd-hostnamed`   | `org.freedesktop.hostname1`         |
+| `ente-timedated-compat`     | `systemd-timedated`   | `org.freedesktop.timedate1`         |
+| `ente-localed-compat`       | `systemd-localed`     | `org.freedesktop.locale1`           |
+| `ente-journald-compat`      | `systemd-journald`    | `org.freedesktop.LogControl1`       |
+| `ente-resolved-compat`      | `systemd-resolved`    | `org.freedesktop.resolve1`          |
+| `ente-polkit-compat`        | `polkitd`             | `org.freedesktop.PolicyKit1`        |
+| `ente-machined-compat`      | `systemd-machined`    | `org.freedesktop.machine1`          |
+| `ente-systemd1-compat`      | `systemd` (Manager)   | `org.freedesktop.systemd1`          |
+| `ente-notify-compat`        | `sd_notify` socket    | `/run/systemd/notify` (datagram)    |
+| `ente-timer-compat`         | `systemd-timer`       | (cron-like, sin D-Bus)              |
+| `ente-tmpfiles-compat`      | `systemd-tmpfiles`    | (aplica tmpfiles.d al boot)         |
+| `ente-binfmt-compat`        | `systemd-binfmt`      | (registra binfmt_misc handlers)     |
+| `ente-policy-provider`      | (interno)             | proveedor de decisiones polkit      |
+
+---
+
+## Crates relacionados fuera de `core/`
+
+Dependen del Init pero viven en `crates/shared/`:
+
+- `ente-incarnate` — rutina pura de `clone(2) + namespaces + cgroup +
+  rlimits + cpu_affinity`. Reusable por shipote y supervisores no-PID-1.
+- `brahman-net` — malla P2P opcional (libp2p) que extiende el handshake.
+- `brahman-sidecar` — helper `spawn(card)` para que las apps se presenten
+  al Init sin reimplementar el cliente del handshake.
+
+## Convención de uso
+
+Para arrancar el Init y ejecutar Cards, ver:
+
+- **Seeds estándar** en `seeds/`.
+- **Build de initramfs** con `scripts/build-arje-initrd.sh`.
+- **Boot en QEMU / bare metal** documentado en `docs/arje-boot.md`.
diff --git a/docs/arje-boot.md b/docs/arje-boot.md
new file mode 100644
index 0000000..023a3a3
--- /dev/null
+++ b/docs/arje-boot.md
@@ -0,0 +1,317 @@
+# Booteando arje — initramfs, QEMU y bare metal
+
+`arje` es el init absorbido por brahman: `ente-zero` corre como PID 1, lee
+la **Tarjeta Semilla** (`/ente/seed.card.json`), monta `/proc /sys /dev
+/sys/fs/cgroup`, y encarna recursivamente cada Card declarada en
+`genesis` vía `ente-incarnate::Incarnator` (`clone(2)` + namespaces + cgroup v2).
+
+Este documento describe el ciclo completo:
+
+1. **Layout** del initramfs.
+2. **Build** del initrd (`scripts/build-arje-initrd.sh`).
+3. **Boot en QEMU** (`scripts/run-arje-qemu.sh`).
+4. **Boot en bare metal / VM persistente** (entrada de GRUB).
+5. **Tarjeta Semilla** — qué pone, qué valida, cómo customizar.
+6. **Debugging**: tracing, sockets de introspección, snapshot/restore.
+
+---
+
+## 1. Layout del initramfs
+
+El initrd es un **CPIO + gzip** (formato `newc`, estándar Linux). Su raíz:
+
+```
+/init                           wrapper sh, ejecuta /sbin/ente-zero
+/sbin/ente-zero                 PID 1
+/usr/sbin/ente-echo
+/usr/sbin/ente-policy-provider
+/usr/sbin/ente-*-compat         13 shims D-Bus (logind, hostnamed, …)
+/ente/seed.card.json            Tarjeta Semilla canónica
+/bin/{sh,ls,mount,…}            busybox-static (o glibc dinámica + libs)
+/etc, /dev, /proc, /sys, /run   mountpoints vacíos
+/sys/fs/cgroup                  mountpoint cgroup v2
+```
+
+Path canónico de la semilla en **prod**: `/ente/seed.card.json` (ver
+`crates/core/ente-zero/src/seed.rs`). En **dev** (no PID 1):
+`./seed.card.json` en el cwd, o se sintetiza una mínima.
+
+## 2. Build del initrd
+
+Requisitos host: `cargo`, `cpio`, `gzip`, y opcionalmente `busybox-static`
+(en Debian/Ubuntu: `apt install busybox-static`).
+
+```bash
+# Default: semilla de prod, sale en out/arje.initrd.cpio.gz
+scripts/build-arje-initrd.sh
+
+# Customizar:
+scripts/build-arje-initrd.sh seeds/arje-minimal.card.json out/min.cpio.gz
+
+# Sin busybox del sistema, apuntar a uno propio:
+BUSYBOX_BIN=/path/to/busybox scripts/build-arje-initrd.sh
+
+# Vendorear binarios extra (separados por espacio):
+EXTRA_BINS="/usr/bin/strace /usr/bin/gdb" scripts/build-arje-initrd.sh
+```
+
+El script:
+
+1. Corre `cargo build --release` para `ente-zero` + los 14 shims compat.
+2. Valida la seed con `seeds/validate.sh` (parse + `Card::validate`).
+3. Stage en un tmpdir → copia binarios + crea mountpoints + escribe `/init`.
+4. Empaqueta `find . | cpio -o -H newc | gzip -9`.
+
+Tamaño típico: ~15-25 MB descomprimido, 5-8 MB el `.cpio.gz`.
+
+## 3. Boot en QEMU
+
+Requisito: un kernel Linux + qemu-system-x86_64.
+
+### 3a. Con kernel del host
+
+```bash
+# Default: usa /boot/vmlinuz-$(uname -r)
+scripts/run-arje-qemu.sh
+
+# Con kernel explícito:
+scripts/run-arje-qemu.sh out/arje.initrd.cpio.gz /boot/vmlinuz-6.6.0
+```
+
+El script invoca:
+
+```
+qemu-system-x86_64
+  -accel kvm                    (si /dev/kvm está, fallback tcg)
+  -m 1024 -smp 2
+  -kernel <vmlinuz>
+  -initrd out/arje.initrd.cpio.gz
+  -append "rdinit=/init console=ttyS0,115200 panic=10"
+  -nographic -serial mon:stdio
+  -no-reboot
+```
+
+`rdinit=/init` le dice al kernel que el primer programa a ejecutar es
+nuestro `/init`, no `/sbin/init`. `console=ttyS0,115200` redirige el log
+del kernel + nuestro tracing a la serial → stdio del host. Salida con
+`Ctrl-A X`.
+
+### 3b. Sin kernel del host
+
+Bajar un kernel mínimo (ej. Ubuntu cloud kernel):
+
+```bash
+wget -O /tmp/vmlinuz \
+  https://cloud-images.ubuntu.com/jammy/current/unpacked/jammy-server-cloudimg-amd64-vmlinuz-generic
+scripts/run-arje-qemu.sh out/arje.initrd.cpio.gz /tmp/vmlinuz
+```
+
+O usar el kernel de Alpine (más liviano, ~10 MB):
+
+```bash
+wget -O /tmp/vmlinuz \
+  https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/netboot/vmlinuz-lts
+```
+
+### 3c. Override del cmdline
+
+`KERNEL_CMD="ente.log=debug"` se concatena al cmdline. Cosas útiles:
+
+| flag                       | efecto                                              |
+| -------------------------- | --------------------------------------------------- |
+| `panic=10`                 | reboot 10 s tras kernel panic (debug)               |
+| `loglevel=7`               | log del kernel hasta debug                          |
+| `quiet`                    | silencia banner kernel                              |
+| `RUST_LOG=trace`           | (no se interpreta; usar env en `/init`)             |
+| `init=/sbin/ente-zero`     | salta `/init`, ejecuta directo (no recomendado)     |
+
+`ente-zero` lee `RUST_LOG` y `BRAHMAN_*` del env. Para setearlas, editar
+`/init` antes de empaquetar, o agregar `-fw_cfg name=opt/foo,...` a qemu.
+
+### 3d. Smoke test esperado
+
+Con `seeds/arje-prod.card.json`:
+
+```
+ente-zero despierta como PID 1
+Tarjeta Semilla cargada y validada path=/ente/seed.card.json
+bus interno escuchando path=/run/ente-bus.sock
+brahman handshake escuchando (Unix) socket=/run/brahman-init.sock
+brahman admin escuchando socket=/run/brahman-admin.sock
+instanciando genesis seed=arje.seed.prod count=16
+Ente compat-logind encarnado pid=...
+Ente compat-hostnamed encarnado pid=...
+...
+arje#                          ← shell en tty
+```
+
+Con `seeds/arje-minimal.card.json`: solo el shell.
+
+## 4. Boot en bare metal / VM persistente
+
+El mismo `.cpio.gz` sirve como initramfs estándar de Linux. Entrada
+GRUB típica (`/etc/grub.d/40_custom` + `update-grub`):
+
+```
+menuentry "arje" {
+    linux  /boot/vmlinuz-6.6.0 rdinit=/init console=tty1 panic=10
+    initrd /boot/arje.initrd.cpio.gz
+}
+```
+
+Copiar a `/boot/`:
+
+```bash
+sudo cp out/arje.initrd.cpio.gz /boot/arje.initrd.cpio.gz
+sudo update-grub
+```
+
+Para una VM (Proxmox, libvirt, etc.) basta con apuntar el "Direct Kernel
+Boot" al vmlinuz y al initrd. En Proxmox: editar `/etc/pve/qemu-server/<vmid>.conf`:
+
+```
+args: -kernel /boot/vmlinuz-6.6.0 -initrd /boot/arje.initrd.cpio.gz \
+      -append "rdinit=/init console=ttyS0,115200 panic=10"
+serial0: socket
+```
+
+### 4a. Sin pivot_root (initramfs es el rootfs final)
+
+El initrd actual **no hace pivot_root** — se queda como rootfs. Esto es
+intencional: arje no asume nada del disco. Para persistencia, las Cards
+hijas deben montar el FS de disco a demanda (ej. `genesis: [{ label:
+"mount-data", payload: Native("/bin/mount", ["/dev/sda1", "/mnt"]), … }]`).
+
+Cuando necesites pivot_root a un FS real (instalación full-disk), agregar
+un Ente que haga `switch_root` antes de instanciar el resto — pendiente
+de implementar como `Capability::SwitchRoot`.
+
+## 5. Tarjeta Semilla — detalles
+
+### 5a. Seeds estándar
+
+| seed                          | uso                                                   |
+| ----------------------------- | ----------------------------------------------------- |
+| `seeds/arje-minimal.card.json` | PID 1 + 1 shell `/bin/sh`. Smoke test para QEMU.     |
+| `seeds/arje-prod.card.json`    | Constelación completa: 14 compat shims + getty.      |
+
+### 5b. Validación
+
+```bash
+seeds/validate.sh seeds/arje-prod.card.json
+```
+
+El script carga la Card vía `ente_brain::load_card_file()` (que llama a
+`brahman_card::Card::validate()`) y verifica que `ente-zero` la encarne
+hasta `instanciando genesis`.
+
+### 5c. Customizar
+
+Estructura de un genesis-child:
+
+```json
+{
+  "schema_version": 1,
+  "id": "<ULID Crockford base32 — no I L O U>",
+  "label": "mi-servicio",
+  "provides": ["Journal"],
+  "requires": [],
+  "permissions": {
+    "networking": "loopback",
+    "filesystem": "read-write",
+    "ipc": { "allow": ["wit-v1"] },
+    "processes": false
+  },
+  "soma": {
+    "namespaces": { "mount": true, "pid": true, "net": false, ...},
+    "rlimits": { "mem_bytes": 268435456 },
+    "cgroup": { "path": "ente.slice/mi-servicio", "cpu_weight": 100 }
+  },
+  "payload": { "Native": { "exec": "/usr/sbin/mi-bin", "argv": [], "envp": [] } },
+  "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+  "lifecycle": "daemon",
+  "priority": "normal",
+  "flow": { "input": [], "output": [] },
+  "genesis": []
+}
+```
+
+Validación clave (`brahman_card::Card::validate`):
+
+- `schema_version == 1`.
+- `label` no vacío, ≤ 256 bytes.
+- ULID válido (Crockford base32, **sin `I L O U`**).
+- `provides ∩ requires == ∅`.
+- `Payload::Native.exec` no vacío.
+- `Payload::Wasm.module_sha256` no todo ceros.
+- rlimits: `mem_bytes > 0 && < 1 TiB`, `nproc ∈ [1, 65535]`, `nofile ∈ [1, 1M]`.
+- `cgroup.cpu_weight`, `io_weight ∈ [1, 10000]`.
+- `flow.input` y `flow.output` con nombres únicos.
+- Recursivo sobre `genesis`.
+
+## 6. Debugging
+
+### 6a. Tracing
+
+`ente-zero` usa `tracing-subscriber`. Default: `ente_zero=debug,info`.
+Override con `RUST_LOG`:
+
+```bash
+# En el initrd: editar /init antes de empaquetar
+echo 'export RUST_LOG="trace"' >> wrapper
+```
+
+### 6b. Sockets de introspección (Unix, dentro del initrd)
+
+| socket                       | servicio                                          |
+| ---------------------------- | ------------------------------------------------- |
+| `/run/ente-bus.sock`          | bus interno (postcard, `BusRequest::Invoke/...`) |
+| `/run/brahman-init.sock`      | handshake brahman                                 |
+| `/run/brahman-admin.sock`     | snapshots de sesiones + matches                   |
+| `/run/ente-brain.sock`        | introspección del cerebro                         |
+
+Conectar desde un Ente hijo: `socat - UNIX-CONNECT:/run/brahman-admin.sock`.
+(El initrd debe tener `socat` o equivalente; agregalo con `EXTRA_BINS`.)
+
+### 6c. Snapshot / restore
+
+```
+ente-zero --checkpoint /ente/checkpoint.json    # escribe al cerrar
+ente-zero --restore    /ente/checkpoint.json    # reconstruye al boot
+```
+
+Snapshot adjunto del cerebro: `/ente/checkpoint.brain.json`.
+
+### 6d. Metrics
+
+```
+ente-zero --metrics-addr 127.0.0.1:9911
+```
+
+Endpoint Prometheus desde dentro de la VM. Para exponerlo al host bajo
+QEMU, agregar `-netdev user,hostfwd=tcp::9911-:9911 -device virtio-net,netdev=…`.
+
+### 6e. Modo DEV en host (sin PID 1)
+
+`ente-zero` detecta si su PID != 1 y entra en **DEV MODE**: no monta
+kernel surface, no se vuelve subreaper, sale tras 4 s. Útil para
+iterar Cards en el host:
+
+```bash
+mkdir /tmp/arje-test && cp seeds/arje-minimal.card.json /tmp/arje-test/seed.card.json
+cd /tmp/arje-test && target/release/ente-zero
+```
+
+---
+
+## Apéndice — checklist pre-deploy
+
+- [ ] `cargo build --release -p ente-zero` sin warnings.
+- [ ] `seeds/validate.sh seeds/arje-prod.card.json` → OK.
+- [ ] `scripts/build-arje-initrd.sh` produce `out/arje.initrd.cpio.gz`.
+- [ ] `scripts/run-arje-qemu.sh` arranca y muestra `Tarjeta Semilla cargada
+      y validada` + `instanciando genesis count=16` (o el count que toque).
+- [ ] Si vas a bare metal: tener un kernel `vmlinuz` rescue (ej. Alpine
+      netboot) en `/boot/` por si arje no levanta.
+- [ ] Para VMs Proxmox/libvirt: serial console habilitada para ver el
+      arranque sin display.
diff --git a/scripts/build-arje-initrd.sh b/scripts/build-arje-initrd.sh
new file mode 100755
index 0000000..fa4363b
--- /dev/null
+++ b/scripts/build-arje-initrd.sh
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+# build-arje-initrd.sh — empaqueta ente-zero + shims compat + Tarjeta Semilla
+# en un initramfs CPIO+gzip listo para arrancar bajo QEMU o como /init real.
+#
+# Layout del initrd resultante:
+#   /init                         → wrapper sh que exec /sbin/ente-zero
+#   /sbin/ente-zero               → PID 1
+#   /usr/sbin/ente-*-compat       → shims systemd
+#   /usr/sbin/ente-echo, ente-policy-provider
+#   /ente/seed.card.json          → Tarjeta Semilla
+#   /bin/{sh,ls,cat,...}          → busybox o glibc-static (depende del flag)
+#   /dev, /proc, /sys, /run       → puntos de montaje (ente-zero los monta)
+#
+# Uso:
+#   scripts/build-arje-initrd.sh [seed.card.json] [out.cpio.gz]
+#
+#   seed     default: seeds/arje-prod.card.json
+#   out      default: out/arje.initrd.cpio.gz
+#
+# Env:
+#   BUSYBOX_BIN   path a un busybox-static (default: $(which busybox))
+#   EXTRA_BINS    binarios extra a copiar, separados por espacio
+#
+# Requisitos: cpio, gzip, ldd (sólo si no usás busybox-static).
+
+set -euo pipefail
+
+SEED="${1:-seeds/arje-prod.card.json}"
+OUT="${2:-out/arje.initrd.cpio.gz}"
+BUSYBOX_BIN="${BUSYBOX_BIN:-$(command -v busybox 2>/dev/null || true)}"
+EXTRA_BINS="${EXTRA_BINS:-}"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+cd "$REPO_DIR"
+
+if [ ! -f "$SEED" ]; then
+  echo "[build-initrd] seed no encontrada: $SEED" >&2
+  exit 2
+fi
+
+# 1. Build release de ente-zero y todos los compat shims.
+echo "[build-initrd] cargo build --release de ente-zero + shims"
+cargo build --release \
+  -p ente-zero \
+  -p ente-echo \
+  -p ente-logind-compat \
+  -p ente-hostnamed-compat \
+  -p ente-timedated-compat \
+  -p ente-localed-compat \
+  -p ente-journald-compat \
+  -p ente-resolved-compat \
+  -p ente-polkit-compat \
+  -p ente-machined-compat \
+  -p ente-systemd1-compat \
+  -p ente-notify-compat \
+  -p ente-timer-compat \
+  -p ente-tmpfiles-compat \
+  -p ente-binfmt-compat \
+  -p ente-policy-provider
+
+# 2. Validar la seed.
+if ! seeds/validate.sh "$SEED" >/dev/null 2>&1; then
+  echo "[build-initrd] seed inválida: $SEED" >&2
+  exit 3
+fi
+
+# 3. Stage root del initrd.
+STAGE="$(mktemp -d -t arje-initrd.XXXXXX)"
+trap 'rm -rf "$STAGE"' EXIT
+mkdir -p "$STAGE"/{bin,sbin,usr/sbin,etc,ente,proc,sys,dev,run,tmp,sys/fs/cgroup}
+
+# 4. Copiar binarios arje.
+install -m 0755 target/release/ente-zero               "$STAGE/sbin/ente-zero"
+for b in ente-echo ente-policy-provider \
+         ente-logind-compat ente-hostnamed-compat ente-timedated-compat \
+         ente-localed-compat ente-journald-compat ente-resolved-compat \
+         ente-polkit-compat ente-machined-compat ente-systemd1-compat \
+         ente-notify-compat ente-timer-compat ente-tmpfiles-compat \
+         ente-binfmt-compat; do
+  install -m 0755 "target/release/$b" "$STAGE/usr/sbin/$b"
+done
+
+# 5. Userspace mínimo (sh, ls, mount, mkdir, ...). Dos rutas:
+#    (a) busybox-static apuntado por $BUSYBOX_BIN → 1 binario, todo simlink.
+#    (b) sin busybox → copiar /bin/sh + deps con ldd (libc dinámica).
+if [ -n "$BUSYBOX_BIN" ] && [ -x "$BUSYBOX_BIN" ]; then
+  echo "[build-initrd] usando busybox-static: $BUSYBOX_BIN"
+  install -m 0755 "$BUSYBOX_BIN" "$STAGE/bin/busybox"
+  ( cd "$STAGE/bin" && for app in sh ls cat mount umount mkdir cp mv \
+       echo grep sed awk ps kill sleep insmod modprobe poweroff reboot \
+       sysctl dmesg ip ifconfig; do
+       ln -sf busybox "$app"
+    done )
+else
+  echo "[build-initrd] sin busybox — copiando /bin/sh + deps via ldd"
+  install -m 0755 /bin/sh "$STAGE/bin/sh"
+  copy_lib() {
+    local lib="$1"
+    [ -f "$lib" ] || return 0
+    local dest="$STAGE${lib}"
+    mkdir -p "$(dirname "$dest")"
+    cp -L "$lib" "$dest"
+  }
+  for b in /bin/sh /bin/ls /bin/cat /bin/mount /bin/umount /bin/mkdir; do
+    [ -x "$b" ] || continue
+    cp -L "$b" "$STAGE${b}"
+    while read -r lib; do copy_lib "$lib"; done < <(
+      ldd "$b" 2>/dev/null | awk '{ for (i=1;i<=NF;i++) if ($i ~ /^\//) print $i }'
+    )
+  done
+fi
+
+# 6. Tarjeta Semilla. Path canónico en prod: /ente/seed.card.json
+install -m 0644 "$SEED" "$STAGE/ente/seed.card.json"
+
+# 7. /init wrapper. El kernel pasa control a /init; nosotros invocamos
+#    ente-zero como PID 1 real con su env mínimo.
+cat > "$STAGE/init" <<'EOF'
+#!/bin/sh
+# arje /init — kernel → este script → ente-zero (PID 1 lo hereda via exec)
+export PATH=/usr/sbin:/usr/bin:/sbin:/bin
+export RUST_LOG="${RUST_LOG:-ente_zero=info,brahman_handshake=info,info}"
+# ente-zero monta /proc /sys /dev /sys/fs/cgroup él mismo.
+exec /sbin/ente-zero
+EOF
+chmod 0755 "$STAGE/init"
+
+# 8. Binarios extra a vendorear.
+if [ -n "$EXTRA_BINS" ]; then
+  for b in $EXTRA_BINS; do
+    [ -x "$b" ] || { echo "[build-initrd] EXTRA_BINS: $b no existe"; exit 4; }
+    install -m 0755 "$b" "$STAGE/usr/sbin/$(basename "$b")"
+  done
+fi
+
+# 9. Empaquetar CPIO + gzip. Formato newc (estándar para Linux initramfs).
+mkdir -p "$(dirname "$OUT")"
+( cd "$STAGE" && find . -print0 | cpio -o -H newc --null --quiet ) | gzip -9 > "$OUT"
+echo "[build-initrd] generado: $OUT ($(du -h "$OUT" | cut -f1))"
diff --git a/scripts/run-arje-qemu.sh b/scripts/run-arje-qemu.sh
new file mode 100755
index 0000000..31cb274
--- /dev/null
+++ b/scripts/run-arje-qemu.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+# run-arje-qemu.sh — bootea el initrd de arje bajo qemu-system-x86_64.
+#
+# Uso:
+#   scripts/run-arje-qemu.sh [initrd] [kernel]
+#
+# Defaults:
+#   initrd  out/arje.initrd.cpio.gz
+#   kernel  $(uname -r) o /boot/vmlinuz-* (primer match)
+#
+# Env:
+#   QEMU         binario de qemu (default: qemu-system-x86_64)
+#   KERNEL_CMD   extra cmdline kernel (se concatena al fijo)
+#   MEM          RAM en MB (default: 1024)
+#   SMP          CPUs virtuales (default: 2)
+#   ACCEL        kvm|tcg (default: kvm si /dev/kvm existe)
+#   HEADLESS     1 → sin display (consola en stdio)
+#
+# El initrd contiene su propio /init → ente-zero corre como PID 1 real.
+
+set -euo pipefail
+
+INITRD="${1:-out/arje.initrd.cpio.gz}"
+KERNEL="${2:-}"
+QEMU="${QEMU:-qemu-system-x86_64}"
+MEM="${MEM:-1024}"
+SMP="${SMP:-2}"
+KERNEL_CMD="${KERNEL_CMD:-}"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+cd "$REPO_DIR"
+
+if [ ! -f "$INITRD" ]; then
+  echo "[run-qemu] initrd no encontrado: $INITRD" >&2
+  echo "         Generalo primero con: scripts/build-arje-initrd.sh" >&2
+  exit 2
+fi
+
+if [ -z "$KERNEL" ]; then
+  # En orden: /boot/vmlinuz-$(uname -r), primer vmlinuz-*, /boot/bzImage.
+  for cand in "/boot/vmlinuz-$(uname -r)" /boot/vmlinuz-* /boot/bzImage*; do
+    if [ -f "$cand" ]; then KERNEL="$cand"; break; fi
+  done
+fi
+
+if [ -z "$KERNEL" ] || [ ! -f "$KERNEL" ]; then
+  echo "[run-qemu] kernel no encontrado." >&2
+  echo "         Pasalo como 2do arg o instalá linux-image-amd64." >&2
+  echo "         También podés bajar uno: " >&2
+  echo "           wget https://kernel.ubuntu.com/...vmlinuz" >&2
+  exit 3
+fi
+
+# KVM si está disponible.
+ACCEL="${ACCEL:-}"
+if [ -z "$ACCEL" ]; then
+  if [ -e /dev/kvm ] && [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
+    ACCEL="kvm"
+  else
+    ACCEL="tcg"
+  fi
+fi
+
+DISPLAY_ARGS=()
+if [ "${HEADLESS:-0}" = "1" ]; then
+  DISPLAY_ARGS+=( -nographic -serial mon:stdio )
+  KERNEL_CMD="console=ttyS0,115200 $KERNEL_CMD"
+else
+  DISPLAY_ARGS+=( -nographic -serial mon:stdio )
+  KERNEL_CMD="console=ttyS0,115200 $KERNEL_CMD"
+fi
+
+CMDLINE="rdinit=/init panic=10 loglevel=4 $KERNEL_CMD"
+
+echo "[run-qemu] kernel: $KERNEL"
+echo "[run-qemu] initrd: $INITRD ($(du -h "$INITRD" | cut -f1))"
+echo "[run-qemu] accel:  $ACCEL"
+echo "[run-qemu] mem:    ${MEM}M, smp: $SMP"
+echo "[run-qemu] cmdline: $CMDLINE"
+echo "[run-qemu] (Ctrl-A X en stdio mode para salir)"
+
+exec "$QEMU" \
+  -accel "$ACCEL" \
+  -m "$MEM" -smp "$SMP" \
+  -kernel "$KERNEL" \
+  -initrd "$INITRD" \
+  -append "$CMDLINE" \
+  "${DISPLAY_ARGS[@]}" \
+  -no-reboot
diff --git a/seeds/arje-minimal.card.json b/seeds/arje-minimal.card.json
new file mode 100644
index 0000000..33533a2
--- /dev/null
+++ b/seeds/arje-minimal.card.json
@@ -0,0 +1,62 @@
+{
+  "schema_version": 1,
+  "id": "01J8YVKZP0M0M0M0M0M0M0M0M1",
+  "lineage": null,
+  "label": "arje.seed.minimal",
+  "provides": ["Spawn", "Journal"],
+  "requires": [],
+  "permissions": {
+    "networking": "none",
+    "filesystem": "read-write",
+    "ipc": { "allow": [] },
+    "processes": true
+  },
+  "soma": {
+    "namespaces": {
+      "mount": false, "pid": false, "net": false,
+      "uts": false, "ipc": false, "user": false, "cgroup": false
+    },
+    "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
+    "cgroup": { "path": "ente.slice/zero", "cpu_weight": null, "io_weight": null },
+    "cpu_affinity": null
+  },
+  "payload": "Virtual",
+  "supervision": "OneShot",
+  "lifecycle": "daemon",
+  "priority": "critical",
+  "flow": { "input": [], "output": [] },
+  "genesis": [
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZP0M0M0M0M0M0M0M0M2",
+      "lineage": null,
+      "label": "shell",
+      "provides": [],
+      "requires": [],
+      "permissions": {
+        "networking": "none",
+        "filesystem": "read-write",
+        "ipc": { "allow": [] },
+        "processes": true
+      },
+      "soma": {
+        "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
+        "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
+        "cgroup": { "path": "ente.slice/shell", "cpu_weight": null, "io_weight": null },
+        "cpu_affinity": null
+      },
+      "payload": {
+        "Native": {
+          "exec": "/bin/sh",
+          "argv": ["-i"],
+          "envp": [["PATH", "/bin:/usr/bin"], ["TERM", "linux"], ["PS1", "arje# "]]
+        }
+      },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon",
+      "priority": "high",
+      "flow": { "input": [], "output": [] },
+      "genesis": []
+    }
+  ]
+}
diff --git a/seeds/arje-prod.card.json b/seeds/arje-prod.card.json
new file mode 100644
index 0000000..f18019e
--- /dev/null
+++ b/seeds/arje-prod.card.json
@@ -0,0 +1,296 @@
+{
+  "schema_version": 1,
+  "id": "01J8YVKZQ0M0M0M0M0M0M0M0M0",
+  "lineage": null,
+  "label": "arje.seed.prod",
+  "provides": ["Spawn", "Journal"],
+  "requires": [],
+  "permissions": {
+    "networking": "loopback",
+    "filesystem": "read-write",
+    "ipc": { "allow": ["wit-v1"] },
+    "processes": true
+  },
+  "soma": {
+    "namespaces": {
+      "mount": false, "pid": false, "net": false,
+      "uts": false, "ipc": false, "user": false, "cgroup": false
+    },
+    "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
+    "cgroup": { "path": "ente.slice/zero", "cpu_weight": null, "io_weight": null },
+    "cpu_affinity": null
+  },
+  "payload": "Virtual",
+  "supervision": "OneShot",
+  "lifecycle": "daemon",
+  "priority": "critical",
+  "flow": { "input": [], "output": [] },
+  "genesis": [
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0M1",
+      "lineage": null,
+      "label": "tmpfiles-boot",
+      "provides": [],
+      "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-tmpfiles-compat", "argv": ["--boot"], "envp": [] } },
+      "supervision": "OneShot",
+      "lifecycle": "oneshot",
+      "priority": "critical",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0M2",
+      "lineage": null,
+      "label": "binfmt-boot",
+      "provides": [],
+      "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-binfmt-compat", "argv": [], "envp": [] } },
+      "supervision": "OneShot",
+      "lifecycle": "oneshot",
+      "priority": "high",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0H0",
+      "lineage": null,
+      "label": "compat-hostnamed",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-only", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-hostnamed-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0T0",
+      "lineage": null,
+      "label": "compat-timedated",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-only", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-timedated-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0X0",
+      "lineage": null,
+      "label": "compat-localed",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-only", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-localed-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0J0",
+      "lineage": null,
+      "label": "compat-journald",
+      "provides": ["Journal"], "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-journald-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "high",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0R0",
+      "lineage": null,
+      "label": "compat-resolved",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "outbound", "filesystem": "read-write", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-resolved-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0P0",
+      "lineage": null,
+      "label": "compat-polkit",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-only", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-polkit-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0Q0",
+      "lineage": null,
+      "label": "policy-provider",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-only", "ipc": { "allow": ["wit-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-policy-provider", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0M0",
+      "lineage": null,
+      "label": "compat-machined",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-only", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-machined-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0S0",
+      "lineage": null,
+      "label": "compat-systemd1",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-write", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-systemd1-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "high",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0N0",
+      "lineage": null,
+      "label": "compat-notify",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-notify-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "normal",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0Y0",
+      "lineage": null,
+      "label": "compat-logind",
+      "provides": ["LegacyLogind"], "requires": [],
+      "permissions": { "networking": "loopback", "filesystem": "read-write", "ipc": { "allow": ["dbus-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-logind-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "high",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0Z0",
+      "lineage": null,
+      "label": "compat-timer",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/compat","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-timer-compat", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 1000, "max": 60000 } },
+      "lifecycle": "daemon", "priority": "low",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0E0",
+      "lineage": null,
+      "label": "echo-smoke",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-only", "ipc": { "allow": ["wit-v1"] }, "processes": false },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/test","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": { "Native": { "exec": "/usr/sbin/ente-echo", "argv": [], "envp": [] } },
+      "supervision": { "Restart": { "initial": 200, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "low",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    },
+    {
+      "schema_version": 1,
+      "id": "01J8YVKZQ0M0M0M0M0M0M0M0G0",
+      "lineage": null,
+      "label": "getty-tty1",
+      "provides": [], "requires": [],
+      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
+      "soma": { "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false},
+                "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null},
+                "cgroup": {"path":"ente.slice/getty","cpu_weight":null,"io_weight":null},
+                "cpu_affinity": null },
+      "payload": {
+        "Native": {
+          "exec": "/bin/sh",
+          "argv": ["-i"],
+          "envp": [["PATH", "/usr/sbin:/usr/bin:/sbin:/bin"], ["TERM", "linux"], ["PS1", "arje# "]]
+        }
+      },
+      "supervision": { "Restart": { "initial": 100, "max": 30000 } },
+      "lifecycle": "daemon", "priority": "high",
+      "flow": { "input": [], "output": [] }, "genesis": []
+    }
+  ]
+}
diff --git a/seeds/validate.sh b/seeds/validate.sh
new file mode 100755
index 0000000..edb2de7
--- /dev/null
+++ b/seeds/validate.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+# Valida una Tarjeta Semilla pasándola por el parser+validate de brahman-card.
+# Uso: seeds/validate.sh seeds/arje-prod.card.json
+#
+# Método: copiamos la Card como `seed.card.json` en un cwd vacío y corremos
+# ente-zero en dev-mode 4 s. Si carga, valida, e instancia genesis, la
+# Card es estructuralmente correcta — los binarios de los hijos pueden
+# faltar en el host (vivirán en /usr/sbin del initrd).
+
+set -euo pipefail
+
+if [ "$#" -ne 1 ]; then
+  echo "uso: $0 <ruta/seed.card.json>" >&2
+  exit 2
+fi
+
+SEED="$(realpath "$1")"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+BIN="$REPO_DIR/target/release/ente-zero"
+
+if [ ! -x "$BIN" ]; then
+  echo "[validate] compilando ente-zero (release)…"
+  (cd "$REPO_DIR" && cargo build --quiet --release -p ente-zero)
+fi
+
+SCRATCH="$(mktemp -d -t arje-validate.XXXXXX)"
+trap 'rm -rf "$SCRATCH"' EXIT
+cp "$SEED" "$SCRATCH/seed.card.json"
+
+cd "$SCRATCH"
+timeout 5 "$BIN" 2>&1 | tee /tmp/arje-validate.log | \
+  grep -E "Tarjeta Semilla cargada|semilla inválida|JSON no contiene|Caused by" | head -5
+
+if grep -q "Tarjeta Semilla cargada y validada" /tmp/arje-validate.log; then
+  echo "[validate] OK: $SEED"
+  exit 0
+fi
+echo "[validate] FALLÓ: $SEED" >&2
+exit 1