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>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
8 archivos · +1046 −0
- .gitignore +1 −0
- crates/core/README.md +100 −0
- docs/arje-boot.md +317 −0
- scripts/build-arje-initrd.sh +140 −0
- scripts/run-arje-qemu.sh +90 −0
- seeds/arje-minimal.card.json +62 −0
- seeds/arje-prod.card.json +296 −0
- seeds/validate.sh +40 −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