gioser

tawasuyu/brahman

Rust 496TOML 195Markdown 55JSON 197001 Definiciones

feat(renaser): Fase 7b — boot siembra la imagen, muere el include_bytes!

El kernel deja de empotrar el userspace por completo. Ya no carga ni un
solo .wasm: es boot quien siembra el disco con el grafo poblado.

- kernel/almacen.rs y manifiesto.rs migran al nucleo compartido `formato`
  (tipos, postcard, BLAKE3, trazado de registros). El kernel pierde los
  include_bytes!, genesis() y sembrar_genesis().
- boot::sembrar_grafo siembra un disco virgen con el bytecode de las apps
  (deduplicado) y el Manifiesto de Genesis anclado en el superbloque.
- cargar_userspace sin rama de siembra; wasm/mod.rs sin TECHO_MEMORIA.
- alias `cargo kernel` -> --manifest-path (esquiva un ICE de cargo con
  formato compartido entre el kernel y boot via artifact-dep).

Verificado en QEMU (screendump): disco virgen -> boot siembra 5 objetos,
el kernel monta su grafo; segundo arranque -> boot respeta el disco, la
cronista persiste. formato: 5/5 pruebas.

Nota: el crate `formato` y los 3 Cargo.toml entraron antes en 43e6b32 por
un `git add -A` de un trabajo concurrente; este commit cierra el resto.

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

7695dbf3ceda3ab6114a460bbae238eae3d91dd8 · por sergio · (hace 5 meses) · padre 43e6b32e1 · ver el árbol en este commit

Delta semántico · 37

renaser/boot/src/main.rs

renaser/kernel/src/almacen.rs

renaser/kernel/src/main.rs

renaser/kernel/src/manifiesto.rs

renaser/kernel/src/wasm/mod.rs

13 archivos · +416 −343

diff --git a/renaser/.cargo/config.toml b/renaser/.cargo/config.toml
index e7ab80c..d8a86ca 100644
--- a/renaser/.cargo/config.toml
+++ b/renaser/.cargo/config.toml
@@ -13,7 +13,12 @@
 bindeps = true
 
 [alias]
-# Compila unicamente el kernel, en aislamiento de arquitectura.
-kernel = "build -p kernel --target x86_64-unknown-none"
+# Compila unicamente el kernel, en aislamiento de arquitectura. Se invoca por
+# `--manifest-path` y NO por `-p kernel`: desde la Fase 7b el kernel y `boot`
+# comparten la crate `formato`, y pedir `-p kernel` dentro del workspace —con
+# el kernel a la vez como dependencia de artefacto— hace caer al resolvedor de
+# features de cargo. Apuntar al manifiesto del kernel lo compila como raiz, sin
+# rozar el grafo de artifact-deps.
+kernel = "build --manifest-path kernel/Cargo.toml --target x86_64-unknown-none"
 # Construye la imagen UEFI y abre QEMU (equivale a `cargo run -p boot`).
 qemu = "run -p boot"
diff --git a/renaser/.gitignore b/renaser/.gitignore
index 39e8e2a..ba78996 100644
--- a/renaser/.gitignore
+++ b/renaser/.gitignore
@@ -2,15 +2,17 @@
 #  renaser :: archivos que NO se versionan
 # =============================================================================
 
-# Artefactos de compilacion de Rust — kernel, boot y apps WASM.
+# Artefactos de compilacion de Rust — kernel, boot, el nucleo `formato` y apps.
 /target
 /kernel/target
+/formato/target
 /apps/*/target
 
-# Bloqueo de dependencias del kernel: se genera al compilarlo en aislamiento.
-# La verdad de versiones la fija el Cargo.lock raiz — el kernel es dependencia
-# de artefacto de `boot` y se resuelve con el.
+# Bloqueo de dependencias generado al compilar el kernel o el nucleo `formato`
+# en aislamiento. La verdad de versiones la fija el Cargo.lock raiz — ambos son
+# dependencias de `boot` y se resuelven con el.
 /kernel/Cargo.lock
+/formato/Cargo.lock
 
 # Ajustes locales del entorno de desarrollo (personales, no compartidos).
 /.claude/settings.local.json
diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md
index 8296d0f..a42b7c9 100644
--- a/renaser/CHANGELOG.md
+++ b/renaser/CHANGELOG.md
@@ -528,3 +528,62 @@ un Manifiesto de Génesis que también vive en el grafo.
     nacidas del grafo», y la cronista pinta su segunda celda — la cuenta de
     arranques sobrevivió al apagón. El userspace nace del grafo en ambos
     casos.
+
+## Fase 7b — La imagen sembrada por `boot`; muere el `include_bytes!` — 2026-05-22
+
+El kernel deja de empotrar el userspace POR COMPLETO. Hasta la 7a, el bytecode
+de las cinco apps de génesis aún viajaba dentro del binario del kernel
+(`include_bytes!`), como semilla transitoria. La 7b lo mata: es el constructor
+de imagen `boot` —en el anfitrión— quien siembra el disco con el grafo ya
+poblado. El binario del kernel ya no carga ni un solo `.wasm`.
+
+### Añadido
+- **`formato`** — nueva crate: un núcleo `#![no_std]` COMPARTIDO que define el
+  formato del grafo de objetos en disco. Tipos (`Objeto`, `SuperBloque`,
+  `Manifiesto`, `EntradaApp`), su (de)serialización `postcard`, la función hash
+  BLAKE3 (escalar pura) y el trazado de un registro del log
+  (`componer_registro` / `longitud_registro`). Lo enlazan el kernel bare-metal
+  Y el anfitrión `boot`: una sola verdad del formato, imposible de divergir
+  entre los dos lados. 5 pruebas de ida y vuelta. Excluida del workspace, como
+  el kernel.
+- `boot`: `sembrar_grafo` — siembra un disco virgen con el grafo ya poblado.
+  Graba el bytecode de cada app de génesis como un objeto (deduplicado:
+  `app.wasm`, usado dos veces, se guarda una sola), compone el Manifiesto de
+  Génesis con sus regiones y cuotas, lo graba con las aristas hacia los objetos
+  de bytecode, y forja el superbloque que lo ancla. El `.wasm` se lee de
+  `kernel/assets/` en tiempo de ejecución.
+
+### Cambiado
+- `kernel/manifiesto.rs`: pierde los cuatro `include_bytes!`, el descriptor
+  `AppGenesis`, `genesis()` y `sembrar_genesis()`. Se reduce a `cargar()` —leer
+  el manifiesto del grafo— y `region()` —traducir la región en disco a la del
+  kernel—. Los tipos `Manifiesto` / `EntradaApp` migran a `formato`.
+- `kernel/almacen.rs`: los tipos `Objeto` / `SuperBloque`, las constantes del
+  formato, el hash y el trazado de registros migran a `formato`. El módulo
+  queda como el almacén VIVO: cursor, índice y E/S contra virtio-blk.
+- `kernel_main` / `cargar_userspace`: sin la rama de siembra. Un disco sin
+  manifiesto anclado ya no se siembra desde el kernel —`boot` lo hace siempre
+  al forjar la imagen—; el kernel se levanta sin userspace y lo reporta.
+- `kernel/Cargo.toml`: deja de declarar `serde` / `postcard` / `blake3` por su
+  cuenta; los hereda —mismas features, BLAKE3 escalar puro— a través de
+  `formato`.
+- `wasm/mod.rs`: se retira la constante `TECHO_MEMORIA`, ya sin uso (el techo
+  por-app lo dicta el manifiesto; el valor de génesis vive ahora en `boot`).
+- `.cargo/config.toml`: el alias `cargo kernel` pasa de `-p kernel` a
+  `--manifest-path kernel/Cargo.toml`. Con `formato` compartido entre el kernel
+  y `boot`, pedir `-p kernel` dentro del workspace —con el kernel a la vez como
+  dependencia de artefacto— hace caer al resolvedor de features de cargo;
+  apuntar al manifiesto del kernel lo compila como raíz y esquiva el grafo.
+
+### Verificado
+- QEMU (captura headless por el monitor, `screendump`), los dos caminos:
+  - **Disco virgen** — `boot` imprime «disco de objetos sembrado :: 5 objetos,
+    manifiesto anclado»; el kernel monta el grafo de `boot` —«grafo montado ::
+    5 objetos :: raíz ausente», SIN «disco formateado» ni «génesis sembrada»—
+    e instancia las cinco apps. Pantalla idéntica a la Fase 6.2.
+  - **Segundo arranque** — `boot` respeta el disco existente («el grafo
+    perdura»); el kernel carga «6 objetos :: raíz presente» y la cronista pinta
+    su segunda celda: la persistencia sobrevive a través de un disco sembrado
+    por `boot`.
+- `cargo build -p boot` y `cargo kernel` compilan limpios; `cargo test` de
+  `formato` — 5/5 en verde.
diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md
index 2400deb..5f25f67 100644
--- a/renaser/CLAUDE.md
+++ b/renaser/CLAUDE.md
@@ -70,9 +70,11 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG).
 
 ## Estado
 
-Fases 1 a 5, la 6.0, la 6.1 completa (sustrato de almacenamiento: sonda PCI,
-HAL/DMA y el grafo de objetos) y la 6.2 (E/S de disco asíncrona por
-interrupción) completadas y verificadas en QEMU. Ver `ROADMAP.md`.
+Fases 1 a 5, 6.0, 6.1 y 6.2 completas; y la Fase 7 —el userspace nace del
+grafo de objetos— en sus sub-fases 7a (Manifiesto de Génesis, carga desde el
+grafo) y 7b (`boot` siembra la imagen, muere el `include_bytes!` del kernel).
+Todo verificado en QEMU. Pendiente: 7c (persistencia inter-sesión). Ver
+`ROADMAP.md`.
 
 ## Flujo de trabajo
 
@@ -84,5 +86,6 @@ En **cada iteración** de trabajo, sin excepción:
 3. Verificar en QEMU si el cambio es observable (con captura de pantalla).
 4. `git commit` (mensaje en español, descriptivo) y `git push origin main`.
 
-El remoto `origin` es `gitea.gioser.net/sergio/renaser`. Mensajes de commit en
-español. Verifica una fase en QEMU antes de darla por cerrada.
+renaser vive ahora dentro del monorepo **brahman**; los commits van al remoto
+de brahman (`gitea.gioser.net/sergio/brahman`). Mensajes de commit en español.
+Verifica una fase en QEMU antes de darla por cerrada.
diff --git a/renaser/Cargo.lock b/renaser/Cargo.lock
index e1e1cd1..6dea5a4 100644
--- a/renaser/Cargo.lock
+++ b/renaser/Cargo.lock
@@ -96,6 +96,7 @@ name = "boot"
 version = "0.1.0"
 dependencies = [
  "bootloader",
+ "formato",
  "kernel",
 ]
 
@@ -328,6 +329,15 @@ dependencies = [
  "ttf-parser",
 ]
 
+[[package]]
+name = "formato"
+version = "0.1.0"
+dependencies = [
+ "blake3",
+ "postcard",
+ "serde",
+]
+
 [[package]]
 name = "funty"
 version = "2.0.0"
@@ -446,16 +456,14 @@ dependencies = [
 name = "kernel"
 version = "0.1.0"
 dependencies = [
- "blake3",
  "bootloader_api",
  "crossbeam-queue",
  "embedded-graphics",
  "fontdue",
+ "formato",
  "futures-util",
  "linked_list_allocator",
  "mirada-layout",
- "postcard",
- "serde",
  "spin",
  "virtio-drivers",
  "wasmi",
diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md
index 4c853d8..3622f08 100644
--- a/renaser/DIARIO.md
+++ b/renaser/DIARIO.md
@@ -263,6 +263,35 @@ puerta que no quiso abrirse.
 A los ojos casi no cambió nada —las mismas cinco ventanas, encendiéndose—.
 Pero por dentro la mudanza fue total: las casas ya no vienen en la maleta.
 
+## El último mueble sale de la maleta
+
+La jornada pasada presumió de una mudanza completa, y mintió un poco. Era
+cierto que el kernel ya abría su cuaderno y traía a los inquilinos desde el
+disco; pero guardaba, doblada en un bolsillo, una copia de todos ellos. Por si
+acaso: si alguna vez despertaba en una casa vacía —un disco recién estrenado—,
+sacaría esa copia y amueblaría él mismo la casa desde cero.
+
+Hoy ese bolsillo se vació. El kernel ya no lleva encima a nadie: ni una copia,
+ni una semilla, ni un recuerdo. Viaja, por fin, ligero de verdad.
+
+¿Y quién amuebla entonces la casa nueva? Quien la construye. Antes, el albañil
+—el que funde los planos y levanta los muros— entregaba las llaves de una casa
+desnuda. Ahora, antes de entregarlas, entra, sienta a cada inquilino en su
+habitación y deja sobre la mesa el cuaderno que dice quién vive dónde y cuánto
+sitio se le presta. El kernel, al llegar, ya no encuentra una casa vacía que
+llenar: encuentra un hogar tibio, y sólo tiene que abrir las puertas.
+
+Para que esto fuera posible hubo que hacer algo callado pero esencial: que el
+albañil y el inquilino hablaran el mismo idioma. Se redactó un pequeño
+diccionario común —cómo se nombra una habitación, cómo se describe a un
+inquilino, cómo se anota una página del cuaderno— y se entregó una copia a
+cada uno. Así, lo que el albañil escribe es, letra por letra, lo que el kernel
+lee: ninguna palabra se pierde en la traducción, porque ya no hay traducción.
+Hay una sola lengua.
+
+A los ojos, otra vez, casi nada cambió: las mismas cinco ventanas. Pero la
+maleta del kernel, esta vez sí, está del todo vacía.
+
 ---
 
 *El diario continúa. La próxima página la escribirá la próxima jornada.*
diff --git a/renaser/FASE7.md b/renaser/FASE7.md
index 1379c0d..dc6783d 100644
--- a/renaser/FASE7.md
+++ b/renaser/FASE7.md
@@ -36,7 +36,7 @@ ataca **incremental**, como las Fases 6.1a/b/c.
 
 ## Sub-fases
 
-### 7a — El Manifiesto y la carga desde el grafo (semilla por el kernel)
+### 7a — El Manifiesto y la carga desde el grafo (semilla por el kernel) — ✅ HECHA
 
 1. **`manifiesto.rs`** *(andamiaje ya creado)* — tipos `Manifiesto` /
    `EntradaApp`, (de)serialización postcard. Hoy es un módulo `no_std` del
@@ -58,7 +58,7 @@ ataca **incremental**, como las Fases 6.1a/b/c.
 7. **Verificar en QEMU** — la pantalla debe verse idéntica a la Fase 6.2
    (cinco apps en sus regiones), pero ahora **nacidas del grafo**.
 
-### 7b — La imagen sembrada por `boot`; muere `include_bytes!`
+### 7b — La imagen sembrada por `boot`; muere `include_bytes!` — ✅ HECHA
 
 - `boot` (anfitrión) aprende el formato del grafo y pre-puebla la imagen de
   disco con los objetos de bytecode + el manifiesto.
diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md
index c00988c..4270df8 100644
--- a/renaser/ROADMAP.md
+++ b/renaser/ROADMAP.md
@@ -97,8 +97,29 @@ gobierna, no por el IOAPIC — basta leer la línea que el firmware ya asignó.
 Verificado en QEMU: el disco se enruta a la IRQ 11; una tarea-sonda del reactor
 lee un bloque de forma asíncrona mientras las apps siguen pintando.
 
-Líneas abiertas posteriores: carga/descarga dinámica de apps desde el grafo de
-objetos; más capacidades del host (temporización, audio).
+## Fase 7 — el userspace nace del Grafo de Objetos
+
+Hasta la Fase 6, el userspace venía **empotrado en el binario del kernel**:
+cuatro `include_bytes!` de `.wasm` y regiones escritas a mano. La Fase 7 lo
+destierra — las aplicaciones pasan a ser objetos del grafo, gobernadas por un
+**Manifiesto de Génesis** que también vive en el grafo. Plan completo en
+`FASE7.md`.
+
+- **7a — el Manifiesto (completada).** `manifiesto.rs`: tipos `Manifiesto` /
+  `EntradaApp` y carga desde el grafo. El superbloque gana el ancla
+  `manifiesto` (VERSION 1→2). `kernel_main` lee el manifiesto e instancia cada
+  app recuperando su bytecode del grafo, verificado por su hash.
+- **7b — la imagen sembrada por `boot` (completada).** Nace la crate
+  `formato`, un núcleo `no_std` con el formato del grafo en disco, COMPARTIDO
+  por el kernel y el constructor de imagen `boot`. `boot` siembra el disco
+  virgen con el grafo ya poblado —bytecode y manifiesto—; el kernel pierde
+  todo `include_bytes!` del userspace. Su binario ya no carga ni un `.wasm`.
+- **7c — persistencia inter-sesión (pendiente).** Una app guarda su estado
+  mutado como un objeto nuevo del grafo; el campo `EntradaApp.estado` lo
+  ancla. Al despertar, la app retoma donde quedó.
+
+Líneas abiertas posteriores: más capacidades del host (temporización, audio);
+la Fase 8 — el compositor sobre `mirada-layout`.
 
 ## Principios que persisten entre fases
 
diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs
index 100a7c6..ca08214 100644
--- a/renaser/boot/src/main.rs
+++ b/renaser/boot/src/main.rs
@@ -3,21 +3,40 @@
 // -----------------------------------------------------------------------------
 //  Un kernel bare-metal no nace solo: alguien debe fusionarlo con un cargador,
 //  sellarlo en una imagen de disco arrancable y entregarlo al hardware. Esa es
-//  la unica mision de este orquestador de ANFITRION.
+//  la mision de este orquestador de ANFITRION.
+//
+//  Desde la Fase 7b hace algo mas: SIEMBRA el grafo. El kernel ya no empotra
+//  el userspace —ni un solo `include_bytes!` de un `.wasm`—; en su lugar, este
+//  constructor pre-puebla el disco de objetos con el bytecode de las apps de
+//  genesis y el Manifiesto de Genesis que dicta cuales arrancan, en que region
+//  y con que cuota. Para ello habla el MISMO formato del grafo que el kernel,
+//  a traves de la crate compartida `formato`.
 //
 //  El flujo es deliberadamente lineal y sin ambiguedad:
 //
 //    1. Localizar el ELF nativo del kernel (lo inyecta la dep. de artefacto).
 //    2. Fusionarlo con el cargador UEFI en una imagen de disco GPT.
-//    3. Lanzar QEMU con esa imagen y el firmware OVMF.
+//    3. Sembrar el disco de objetos: el grafo poblado con el bytecode del
+//       userspace y el Manifiesto de Genesis (Fase 7b).
+//    4. Lanzar QEMU con la imagen, el disco de objetos y el firmware OVMF.
 //
 //  Cada paso que pueda fallar lo hace en voz alta, con un mensaje accionable:
 //  preferimos un error claro a un arranque silencioso hacia la nada.
 // =============================================================================
 
+use std::collections::BTreeMap;
+use std::io::Write;
 use std::path::{Path, PathBuf};
 use std::process::Command;
 
+// El formato del grafo de objetos en disco — el MISMO nucleo `no_std` que
+// enlaza el kernel. Gracias a el, lo que `boot` siembra y lo que el kernel lee
+// es, byte a byte, el mismo idioma.
+use formato::{
+    EntradaApp, Hash, Manifiesto, Objeto, SuperBloque, MAGIA, MAX_OBJETO, TAM_SECTOR,
+    VERSION_MANIFIESTO, VERSION_SUPERBLOQUE,
+};
+
 /// Ruta del ELF del kernel, ya compilado para `x86_64-unknown-none`.
 ///
 /// La dependencia de artefacto define esta variable de entorno en tiempo de
@@ -35,7 +54,8 @@ const NOMBRE_IMAGEN: &str = "renaser-uefi.img";
 /// directorio de trabajo —la raiz del repo—, comun a `boot` y a QEMU.
 const NOMBRE_DISCO: &str = "target/disk.img";
 
-/// Tamaño del disco de objetos: 32 MiB. Se crea como fichero disperso.
+/// Tamaño del disco de objetos: 32 MiB. La imagen sembrada ocupa solo unos
+/// pocos KiB; el resto queda a cero —espacio libre para que el grafo crezca—.
 const TAM_DISCO: u64 = 32 * 1024 * 1024;
 
 fn main() {
@@ -47,7 +67,7 @@ fn main() {
     }
 }
 
-/// Ejecuta, en orden, las tres operaciones de la Fase 1.5.
+/// Ejecuta, en orden, las operaciones de la Fase 1.5.
 fn orquestar() -> Result<(), String> {
     // --- 1. Localizar el artefacto del kernel. ---
     let kernel = Path::new(KERNEL_ELF);
@@ -67,7 +87,7 @@ fn orquestar() -> Result<(), String> {
         .create_disk_image(&imagen)
         .map_err(|e| format!("la crate `bootloader` no pudo crear la imagen UEFI: {e:?}"))?;
 
-    // --- 3. Garantizar el disco de objetos del grafo persistente. ---
+    // --- 3. Garantizar —y, si es virgen, SEMBRAR— el disco de objetos. ---
     preparar_disco_objetos()?;
 
     // --- 4. Lanzar QEMU sobre esa imagen. ---
@@ -75,35 +95,185 @@ fn orquestar() -> Result<(), String> {
     lanzar_qemu(&imagen, &ovmf)
 }
 
-/// Garantiza la existencia del disco de objetos del grafo persistente. Si no
-/// existe, lo forja como un fichero disperso de 32 MiB, ENTERAMENTE A CERO: el
-/// kernel, al no hallar la firma de su superbloque, lo formateara como un grafo
-/// virgen. Si ya existe, lo respeta — el grafo perdura entre arranques.
+// =============================================================================
+//  Fase 7b — la siembra del grafo: el userspace nace de la imagen de disco
+// =============================================================================
+
+/// Una app de genesis: su nombre legible, el `.wasm` que la encarna y la
+/// ventana del framebuffer que habitara — `(x, y, ancho, alto)` en pixeles.
+struct AppGenesis {
+    nombre: &'static str,
+    archivo: &'static str,
+    region: (u32, u32, u32, u32),
+}
+
+/// El userspace de genesis — las cinco aplicaciones que pueblan un disco recien
+/// forjado, con las regiones de la Fase 6.2. `app.wasm` aparece dos veces —dos
+/// instancias del mismo bytecode—; el grafo, direccionado por contenido, guarda
+/// su objeto una sola vez.
+const GENESIS: [AppGenesis; 5] = [
+    AppGenesis { nombre: "hola-izq", archivo: "app.wasm", region: (100, 120, 480, 560) },
+    AppGenesis { nombre: "hola-der", archivo: "app.wasm", region: (700, 120, 480, 560) },
+    AppGenesis { nombre: "discola", archivo: "discola.wasm", region: (60, 700, 360, 80) },
+    AppGenesis { nombre: "glotona", archivo: "glotona.wasm", region: (460, 700, 360, 80) },
+    AppGenesis { nombre: "cronista", archivo: "cronista.wasm", region: (860, 700, 360, 80) },
+];
+
+/// Techo de memoria lineal de cada app de genesis: 4 MiB. Un modulo que intente
+/// crecer su memoria mas alla es desalojado por el kernel.
+const TECHO_GENESIS: u32 = 4 * 1024 * 1024;
+
+/// Garantiza la existencia del disco de objetos del grafo persistente. Si ya
+/// existe, lo RESPETA — el grafo perdura entre arranques (la cuenta de la
+/// cronista, el estado del userspace). Si no existe, lo forja Y LO SIEMBRA:
+/// graba el grafo ya poblado con el bytecode de las apps y su Manifiesto de
+/// Genesis. El kernel jamas vuelve a empotrar una sola app.
 fn preparar_disco_objetos() -> Result<(), String> {
     let disco = Path::new(NOMBRE_DISCO);
     if disco.is_file() {
-        println!("[renaser/boot] disco de objetos presente :: {}", disco.display());
+        println!(
+            "[renaser/boot] disco de objetos presente :: {} — el grafo perdura",
+            disco.display()
+        );
         return Ok(());
     }
     if let Some(directorio) = disco.parent() {
         std::fs::create_dir_all(directorio)
             .map_err(|e| format!("no se pudo crear el directorio del disco de objetos: {e}"))?;
     }
-    // Forjar el disco: un fichero disperso, a cero, de 32 MiB. El kernel
-    // escribira su superbloque la primera vez que lo monte.
-    let fichero = std::fs::File::create(disco)
+
+    // Sembrar el grafo: el bytecode del userspace y el Manifiesto de Genesis.
+    let (imagen, objetos) = sembrar_grafo()?;
+    if imagen.len() as u64 > TAM_DISCO {
+        return Err(format!(
+            "el grafo sembrado ({} bytes) no cabe en el disco de objetos ({TAM_DISCO} bytes)",
+            imagen.len()
+        ));
+    }
+
+    // Escribir la imagen sembrada y extender el fichero a 32 MiB: el log queda
+    // al principio; el resto, a cero —`set_len` lo deja disperso—.
+    let mut fichero = std::fs::File::create(disco)
         .map_err(|e| format!("no se pudo crear el disco de objetos «{}»: {e}", disco.display()))?;
+    fichero
+        .write_all(&imagen)
+        .map_err(|e| format!("no se pudo escribir el grafo sembrado: {e}"))?;
     fichero
         .set_len(TAM_DISCO)
         .map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?;
     println!(
-        "[renaser/boot] disco de objetos forjado :: {} ({} MiB, virgen)",
-        disco.display(),
-        TAM_DISCO / (1024 * 1024)
+        "[renaser/boot] disco de objetos sembrado :: {} ({objetos} objetos, manifiesto anclado)",
+        disco.display()
     );
     Ok(())
 }
 
+/// Lee el bytecode `.wasm` de una app de genesis desde `kernel/assets/`. La
+/// ruta se ancla al directorio de ESTE crate —no al de trabajo—: el
+/// constructor funciona se invoque desde donde se invoque.
+fn leer_wasm(archivo: &str) -> Result<Vec<u8>, String> {
+    let ruta = Path::new(env!("CARGO_MANIFEST_DIR"))
+        .join("../kernel/assets")
+        .join(archivo);
+    std::fs::read(&ruta)
+        .map_err(|e| format!("no se pudo leer el bytecode «{}»: {e}", ruta.display()))
+}
+
+/// Anexa un objeto al log: compone su registro `[longitud][payload][relleno]`,
+/// lo añade a la imagen y avanza el cursor. Devuelve el hash del objeto — su
+/// identidad en el grafo direccionado por contenido.
+fn anexar_objeto(log: &mut Vec<u8>, cursor: &mut u64, payload: &[u8]) -> Result<Hash, String> {
+    if payload.is_empty() || payload.len() > MAX_OBJETO {
+        return Err(format!(
+            "un objeto del grafo tiene un tamaño invalido: {} bytes",
+            payload.len()
+        ));
+    }
+    let hash = formato::hash(payload);
+    log.extend_from_slice(&formato::componer_registro(payload));
+    *cursor += formato::sectores_registro(payload.len());
+    Ok(hash)
+}
+
+/// Siembra el grafo de objetos de un disco virgen: graba el bytecode de cada
+/// app de genesis como un objeto del grafo, compone el Manifiesto de Genesis
+/// —con sus regiones y cuotas—, lo graba con las aristas hacia los objetos de
+/// bytecode, y forja el superbloque que lo ancla. Devuelve la imagen del disco
+/// (superbloque en el sector 0 + el log de registros) y el numero de objetos
+/// sembrados. Habla, byte a byte, el formato que el kernel leera al montar.
+fn sembrar_grafo() -> Result<(Vec<u8>, usize), String> {
+    // El log de registros: del sector 1 en adelante. El sector 0 es el
+    // superbloque, que aun no podemos escribir —no conocemos el cursor final—.
+    let mut log: Vec<u8> = Vec::new();
+    let mut cursor: u64 = 1;
+
+    // --- 1. Los objetos de bytecode, DEDUPLICADOS por archivo. Dos apps que
+    //        comparten el mismo `.wasm` comparten un unico objeto del grafo. ---
+    let mut hash_de: BTreeMap<&str, Hash> = BTreeMap::new();
+    let mut hijos_manifiesto: Vec<Hash> = Vec::new();
+    let mut apps: Vec<EntradaApp> = Vec::new();
+
+    for app in &GENESIS {
+        let bytecode = match hash_de.get(app.archivo) {
+            // Ya grabado: el grafo no guarda dos veces el mismo contenido.
+            Some(&hash) => hash,
+            None => {
+                let datos = leer_wasm(app.archivo)?;
+                let objeto = Objeto { datos, hijos: Vec::new() };
+                let payload = objeto.serializar().map_err(|e| e.to_string())?;
+                let hash = anexar_objeto(&mut log, &mut cursor, &payload)?;
+                hash_de.insert(app.archivo, hash);
+                hijos_manifiesto.push(hash);
+                hash
+            }
+        };
+        let (x, y, ancho, alto) = app.region;
+        apps.push(EntradaApp {
+            nombre: app.nombre.to_string(),
+            bytecode,
+            region_x: x,
+            region_y: y,
+            region_ancho: ancho,
+            region_alto: alto,
+            techo_memoria: TECHO_GENESIS,
+            estado: None,
+        });
+    }
+
+    // --- 2. El objeto del Manifiesto de Genesis. Sus `hijos` son los objetos
+    //        de bytecode: el grafo lo lee como el nodo padre del userspace. ---
+    let manifiesto = Manifiesto { version: VERSION_MANIFIESTO, apps };
+    let man_datos = manifiesto.serializar().map_err(|e| e.to_string())?;
+    let man_objeto = Objeto { datos: man_datos, hijos: hijos_manifiesto };
+    let man_payload = man_objeto.serializar().map_err(|e| e.to_string())?;
+    let hash_manifiesto = anexar_objeto(&mut log, &mut cursor, &man_payload)?;
+
+    // El grafo sembrado: un objeto por cada `.wasm` unico, mas el manifiesto.
+    let objetos = hash_de.len() + 1;
+
+    // --- 3. El superbloque: el ancla del grafo, en el sector 0. `raiz` queda
+    //        vacia —el userspace la fija; `manifiesto` apunta a la genesis. ---
+    let superbloque = SuperBloque {
+        magia: MAGIA,
+        version: VERSION_SUPERBLOQUE,
+        cursor,
+        raiz: None,
+        manifiesto: Some(hash_manifiesto),
+    };
+    let sb_bytes = superbloque.serializar().map_err(|e| e.to_string())?;
+    if sb_bytes.len() > TAM_SECTOR {
+        return Err("el superbloque sembrado no cabe en un sector".to_string());
+    }
+
+    // La imagen: el superbloque en el sector 0 (relleno a cero) y, tras el, el
+    // log de registros que acabamos de componer.
+    let mut imagen = vec![0u8; TAM_SECTOR];
+    imagen[..sb_bytes.len()].copy_from_slice(&sb_bytes);
+    imagen.extend_from_slice(&log);
+
+    Ok((imagen, objetos))
+}
+
 /// Calcula la ruta de la imagen: junto al propio ELF del kernel, es decir,
 /// dentro de `target/`. Una ubicacion predecible y siempre escribible.
 fn ruta_imagen(kernel: &Path) -> PathBuf {
diff --git a/renaser/kernel/src/almacen.rs b/renaser/kernel/src/almacen.rs
index 95bd5de..a2ce6b2 100644
--- a/renaser/kernel/src/almacen.rs
+++ b/renaser/kernel/src/almacen.rs
@@ -16,63 +16,27 @@
 //  El disco se organiza como un LOG: el sector 0 es el superbloque —el ancla
 //  del grafo—, y tras el se anexan los registros de objetos, uno tras otro. Un
 //  indice en memoria (hash -> sector) se reconstruye al arrancar recorriendo el
-//  log. La serializacion la hace `postcard`: binaria, compacta, determinista.
+//  log.
+//
+//  El FORMATO en disco —los tipos `Objeto`/`SuperBloque`, su (de)serializacion
+//  `postcard`, el hash BLAKE3 y el trazado de cada registro— ya no vive aqui:
+//  habita la crate `formato` (Fase 7b), un nucleo `no_std` COMPARTIDO con el
+//  constructor de imagen `boot`. Este modulo es solo el almacen VIVO: el
+//  cursor, el indice y la E/S contra el disco virtio-blk.
 // =============================================================================
 
 use alloc::collections::BTreeMap;
 use alloc::vec;
 use alloc::vec::Vec;
 
-use serde::{Deserialize, Serialize};
 use spin::{Mutex, Once};
 
-use crate::drivers::disco::{self, TAM_SECTOR};
-
-/// Firma magica del superbloque — «RENASer GRaFo». Distingue un disco de
-/// renaser de uno virgen o ajeno.
-const MAGIA: [u8; 8] = *b"RENASGRF";
-
-/// Version del formato en disco. Un disco con otra version se reformatea.
-/// v2 (Fase 7) — el superbloque gana el ancla `manifiesto`; un disco v1 se
-/// reformatea al arrancar, como cualquier disco ajeno.
-const VERSION: u32 = 2;
-
-/// Techo del tamaño de un objeto serializado: 1 MiB. Acota los buferes de E/S
-/// y permite descartar un registro corrupto sin intentar leer un disparate.
-const MAX_OBJETO: usize = 1024 * 1024;
+use crate::drivers::disco;
 
-/// El identificador de un objeto: el hash BLAKE3 de su forma serializada. En un
-/// almacen direccionado por contenido, la identidad ES el contenido.
-pub type Hash = [u8; 32];
-
-/// Un objeto del grafo: una carga util opaca y las aristas que lo enlazan con
-/// otros objetos. Los `hijos` hacen del almacen un DAG —no un arbol, no una
-/// lista—: un objeto puede ser hijo de muchos, y el direccionamiento por
-/// contenido garantiza que cada contenido distinto se guarda una sola vez.
-#[derive(Serialize, Deserialize, Clone)]
-pub struct Objeto {
-    /// La carga util del objeto: bytes crudos, que el kernel no interpreta.
-    pub datos: Vec<u8>,
-    /// Los hashes de los objetos hijos: las aristas salientes del DAG.
-    pub hijos: Vec<Hash>,
-}
-
-/// El superbloque: el sector 0 del disco. Ancla el grafo entero — dice por
-/// donde continua el log, cual es el objeto raiz y cual el manifiesto.
-#[derive(Serialize, Deserialize)]
-struct SuperBloque {
-    /// Firma magica: debe ser [`MAGIA`].
-    magia: [u8; 8],
-    /// Version del formato: debe ser [`VERSION`].
-    version: u32,
-    /// Proximo sector libre del log — donde se anexara el siguiente objeto.
-    cursor: u64,
-    /// El objeto raiz del DAG: el punto de entrada que el userspace fija y lee.
-    raiz: Option<Hash>,
-    /// El Manifiesto de Genesis (Fase 7): el objeto que dicta que apps nacen
-    /// del grafo al arrancar. Ancla del kernel, gemela de `raiz` (del userspace).
-    manifiesto: Option<Hash>,
-}
+// El identificador y el objeto del grafo los define `formato`; se reexportan
+// para que el resto del kernel siga nombrandolos `almacen::Hash` y
+// `almacen::Objeto`, sin enterarse de donde viven realmente.
+pub use formato::{Hash, Objeto};
 
 /// El estado vivo del almacen: el cursor del log, la raiz, el manifiesto y el
 /// indice en memoria que traduce cada hash al sector donde habita su registro.
@@ -104,12 +68,6 @@ pub struct Resumen {
     pub formateado: bool,
 }
 
-/// Numero de sectores que ocupa un registro cuyo payload mide `longitud` bytes.
-/// Cada registro en disco es: `[longitud: u32 LE][payload postcard][relleno 0]`.
-fn sectores_registro(longitud: usize) -> u64 {
-    (4 + longitud).div_ceil(TAM_SECTOR) as u64
-}
-
 /// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el
 /// disco ya es de renaser, reconstruye el indice recorriendo el log; si es
 /// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`.
@@ -120,13 +78,13 @@ pub fn init() -> Result<Resumen, &'static str> {
     }
 
     // Leer el sector 0 e intentar interpretarlo como superbloque de renaser.
-    let mut sector0 = [0u8; TAM_SECTOR];
+    let mut sector0 = [0u8; formato::TAM_SECTOR];
     disco::leer_sectores(0, &mut sector0)?;
 
     let (cursor, raiz, manifiesto, indice, formateado) =
-        match postcard::take_from_bytes::<SuperBloque>(&sector0) {
+        match formato::SuperBloque::deserializar(&sector0) {
             // Disco de renaser, con la version corriente: adoptar su grafo.
-            Ok((sb, _)) if sb.magia == MAGIA && sb.version == VERSION => {
+            Ok(sb) if sb.magia == formato::MAGIA && sb.version == formato::VERSION_SUPERBLOQUE => {
                 let indice = reconstruir_indice(sb.cursor)?;
                 (sb.cursor, sb.raiz, sb.manifiesto, indice, false)
             }
@@ -166,13 +124,11 @@ fn reconstruir_indice(cursor: u64) -> Result<BTreeMap<Hash, u64>, &'static str>
     let mut indice = BTreeMap::new();
     let mut sector: u64 = 1;
     while sector < cursor {
-        let payload = leer_registro(sector)?;
-        match payload {
+        match leer_registro(sector)? {
             // Un payload valido: hashearlo e indexarlo.
             Some(payload) => {
-                let n = sectores_registro(payload.len());
-                let hash = *blake3::hash(&payload).as_bytes();
-                indice.insert(hash, sector);
+                let n = formato::sectores_registro(payload.len());
+                indice.insert(formato::hash(&payload), sector);
                 sector += n;
             }
             // Cabecera a cero o longitud imposible: fin (o corrupcion) del log.
@@ -186,19 +142,18 @@ fn reconstruir_indice(cursor: u64) -> Result<BTreeMap<Hash, u64>, &'static str>
 /// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice
 /// longitud cero —fin del log— o una longitud imposible —corrupcion—.
 fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> {
-    let mut cabecera = [0u8; TAM_SECTOR];
+    let mut cabecera = [0u8; formato::TAM_SECTOR];
     disco::leer_sectores(sector, &mut cabecera)?;
-    let longitud =
-        u32::from_le_bytes([cabecera[0], cabecera[1], cabecera[2], cabecera[3]]) as usize;
-    if longitud == 0 || longitud > MAX_OBJETO {
-        return Ok(None);
-    }
-    let n = sectores_registro(longitud) as usize;
+    let longitud = match formato::longitud_registro(&cabecera) {
+        Some(longitud) => longitud,
+        None => return Ok(None),
+    };
+    let n = formato::sectores_registro(longitud) as usize;
     // Si el registro cabe en el sector ya leido, evitar una segunda lectura.
     let payload = if n == 1 {
         cabecera[4..4 + longitud].to_vec()
     } else {
-        let mut buf = vec![0u8; n * TAM_SECTOR];
+        let mut buf = vec![0u8; n * formato::TAM_SECTOR];
         disco::leer_sectores(sector, &mut buf)?;
         buf[4..4 + longitud].to_vec()
     };
@@ -207,18 +162,18 @@ fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> {
 
 /// Graba el superbloque —el ancla del grafo— en el sector 0.
 fn persistir(almacen: &Almacen) -> Result<(), &'static str> {
-    let sb = SuperBloque {
-        magia: MAGIA,
-        version: VERSION,
+    let sb = formato::SuperBloque {
+        magia: formato::MAGIA,
+        version: formato::VERSION_SUPERBLOQUE,
         cursor: almacen.cursor,
         raiz: almacen.raiz,
         manifiesto: almacen.manifiesto,
     };
-    let bytes = postcard::to_allocvec(&sb).map_err(|_| "no se pudo serializar el superbloque")?;
-    if bytes.len() > TAM_SECTOR {
+    let bytes = sb.serializar()?;
+    if bytes.len() > formato::TAM_SECTOR {
         return Err("el superbloque no cabe en un sector");
     }
-    let mut sector0 = [0u8; TAM_SECTOR];
+    let mut sector0 = [0u8; formato::TAM_SECTOR];
     sector0[..bytes.len()].copy_from_slice(&bytes);
     disco::escribir_sectores(0, &sector0)
 }
@@ -228,13 +183,12 @@ fn persistir(almacen: &Almacen) -> Result<(), &'static str> {
 /// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo.
 pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> {
     let objeto = Objeto { datos, hijos };
-    let bytes =
-        postcard::to_allocvec(&objeto).map_err(|_| "no se pudo serializar el objeto")?;
-    if bytes.is_empty() || bytes.len() > MAX_OBJETO {
+    let bytes = objeto.serializar()?;
+    if bytes.is_empty() || bytes.len() > formato::MAX_OBJETO {
         return Err("el objeto tiene un tamaño invalido");
     }
     // La identidad del objeto: el hash de su forma serializada.
-    let hash = *blake3::hash(&bytes).as_bytes();
+    let hash = formato::hash(&bytes);
 
     let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
     let mut almacen = mutex.lock();
@@ -245,16 +199,14 @@ pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str>
     }
 
     // Reservar los sectores del registro al final del log.
-    let n = sectores_registro(bytes.len());
+    let n = formato::sectores_registro(bytes.len());
     if almacen.cursor + n > almacen.capacidad {
         return Err("el grafo de objetos esta lleno");
     }
     let sector = almacen.cursor;
 
-    // Componer el registro: [longitud][payload][relleno a cero] y grabarlo.
-    let mut registro = vec![0u8; n as usize * TAM_SECTOR];
-    registro[0..4].copy_from_slice(&(bytes.len() as u32).to_le_bytes());
-    registro[4..4 + bytes.len()].copy_from_slice(&bytes);
+    // Componer el registro —[longitud][payload][relleno]— y grabarlo.
+    let registro = formato::componer_registro(&bytes);
     disco::escribir_sectores(sector, &registro)?;
 
     // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el
@@ -279,12 +231,10 @@ pub fn recuperar(hash: &Hash) -> Result<Option<Objeto>, &'static str> {
     let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?;
     // Verificacion de integridad: el contenido leido DEBE rehashear al hash
     // pedido. Si no, el disco ha mentido — y se delata.
-    if *blake3::hash(&payload).as_bytes() != *hash {
+    if formato::hash(&payload) != *hash {
         return Err("el objeto no supero la verificacion de integridad");
     }
-    let (objeto, _) = postcard::take_from_bytes::<Objeto>(&payload)
-        .map_err(|_| "no se pudo deserializar el objeto")?;
-    Ok(Some(objeto))
+    Ok(Some(Objeto::deserializar(&payload)?))
 }
 
 /// El hash del objeto raiz del grafo, si lo hay.
@@ -309,6 +259,7 @@ pub fn manifiesto() -> Option<Hash> {
 
 /// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el
 /// superbloque. Gemelo de [`fijar_raiz`].
+#[allow(dead_code)] // Lo usara la Fase 7c (persistencia inter-sesion).
 pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> {
     let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
     let mut almacen = mutex.lock();
diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs
index 04de05d..d21103a 100644
--- a/renaser/kernel/src/main.rs
+++ b/renaser/kernel/src/main.rs
@@ -134,7 +134,7 @@ async fn tarea_sonda_disco() {
 /// corrupto, o la carga fracasa, se salda pintando la region de la app con
 /// la baliza de desalojo — el kernel no se inmuta y sigue con las demas.
 fn encender_app(ejecutor: &mut Executor, entrada: &manifiesto::EntradaApp) {
-    let region = entrada.region();
+    let region = manifiesto::region(entrada);
     // Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
     // objeto y verifica su integridad: un bytecode corrupto se delata aqui
     // —y la app se niega, no se instancia un modulo en el que no se confia.
@@ -163,24 +163,20 @@ fn reportar(linea: &str) {
 }
 
 /// FASE 7 :: puebla el userspace DESDE EL GRAFO. Carga el Manifiesto de
-/// Genesis; si el disco no tiene uno —disco virgen—, lo siembra y lo vuelve a
-/// cargar. Por cada `EntradaApp`, enciende su aplicacion. Toda falla se
-/// reporta a la consola y NO detiene el arranque: el kernel se levanta con
-/// las apps que pueda — o con ninguna, si el grafo no tiene userspace.
+/// Genesis que `boot` sembro en la imagen de disco y, por cada `EntradaApp`,
+/// enciende su aplicacion. Toda falla se reporta a la consola y NO detiene el
+/// arranque: el kernel se levanta con las apps que pueda — o con ninguna, si
+/// el grafo no tiene userspace.
 fn cargar_userspace(ejecutor: &mut Executor) {
     let manifiesto = match manifiesto::cargar() {
         Ok(Some(m)) => Some(m),
-        // Disco sin manifiesto: sembrar la genesis y volver a cargarlo.
-        Ok(None) => match manifiesto::sembrar_genesis() {
-            Ok(_) => {
-                reportar("manifiesto :: genesis sembrada en disco virgen");
-                manifiesto::cargar().ok().flatten()
-            }
-            Err(motivo) => {
-                reportar(&format!("manifiesto :: siembra fallida -- {motivo}"));
-                None
-            }
-        },
+        // Disco sin manifiesto anclado: `boot` no lo sembro. El kernel se
+        // levanta sin userspace —pero se levanta—; en la practica, ninguna
+        // imagen forjada por `boot` llega aqui sin su Manifiesto de Genesis.
+        Ok(None) => {
+            reportar("manifiesto :: el disco no trae uno -- el kernel se levanta solo");
+            None
+        }
         Err(motivo) => {
             reportar(&format!("manifiesto :: carga fallida -- {motivo}"));
             None
@@ -333,10 +329,10 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
 
     // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL
     //        GRAFO. El kernel ya no empotra los modulos WASM: lee el
-    //        Manifiesto de Genesis —si el disco esta virgen, lo siembra— e
+    //        Manifiesto de Genesis que `boot` sembro en la imagen de disco e
     //        instancia cada `EntradaApp` recuperando su bytecode del grafo de
     //        objetos. Las cinco apps de genesis (dos instancias de hello, la
-    //        discola, la glotona y la cronista) nacen ahora del disco, no del
+    //        discola, la glotona y la cronista) nacen del disco, no del
     //        binario del kernel.
     //
     //        Las interrupciones se habilitan AHORA: el temporizador marcara el
diff --git a/renaser/kernel/src/manifiesto.rs b/renaser/kernel/src/manifiesto.rs
index 20c3714..60586ed 100644
--- a/renaser/kernel/src/manifiesto.rs
+++ b/renaser/kernel/src/manifiesto.rs
@@ -3,216 +3,54 @@
 // -----------------------------------------------------------------------------
 //  Hasta la Fase 6, el userspace venia EMPOTRADO en el binario del kernel:
 //  `include_bytes!` de cada `.wasm` y regiones escritas a mano. La Fase 7 lo
-//  destierra: las aplicaciones pasan a ser OBJETOS DEL GRAFO, y lo que arranca
-//  —con que cuota, en que region— lo dicta este Manifiesto de Genesis, que
-//  tambien habita el grafo. El superbloque guarda su hash en un ancla propia.
+//  desterro: las aplicaciones son OBJETOS DEL GRAFO, y lo que arranca —con que
+//  cuota, en que region— lo dicta este Manifiesto de Genesis, que tambien
+//  habita el grafo. El superbloque guarda su hash en un ancla propia.
 //
-//  El kernel, al despertar: lee el ancla del superbloque, recupera el objeto
-//  del manifiesto, lo deserializa, y por cada `EntradaApp` recupera el objeto
-//  de bytecode —verificado por su hash— y lo inyecta en `wasmi`.
+//  ESTADO: Fase 7b. El kernel ya NO empotra una sola app. La siembra de la
+//  imagen —grabar los objetos de bytecode y el manifiesto en un disco virgen—
+//  la hace por completo el constructor de imagen `boot`, en el anfitrion. Este
+//  modulo se reduce a su esencia: LEER el manifiesto del grafo al arrancar.
 //
-//  ESTADO: Fase 7a. Tipos, (de)serializacion, carga desde el grafo y siembra
-//  de la genesis, implementados. La siembra es TRANSITORIA — el bytecode aun
-//  viaja empotrado (`include_bytes!`, abajo); la Fase 7b lo movera al
-//  constructor de imagen `boot` y el kernel dejara de empotrar una sola app.
-//  Ver `FASE7.md` para el plan completo.
+//  Los tipos `Manifiesto` / `EntradaApp` y su (de)serializacion viven en la
+//  crate `formato`, el nucleo `no_std` compartido con `boot`. Aqui solo queda
+//  lo que es del kernel: recuperar el manifiesto del grafo y traducir las
+//  regiones de su formato en disco (`u32`) a la `RegionPantalla` del kernel.
 // =============================================================================
 
-use alloc::string::String;
-use alloc::vec::Vec;
-
-use serde::{Deserialize, Serialize};
-
-use crate::almacen::Hash;
+use crate::almacen;
 use crate::grafico::RegionPantalla;
 
-/// Version del formato del manifiesto serializado. Independiente de la
-/// version del superbloque (`almacen::VERSION`): el manifiesto es un objeto
-/// del grafo, no una estructura de disco.
-pub const VERSION_MANIFIESTO: u32 = 1;
-
-/// El Manifiesto de Genesis: la lista de aplicaciones que el kernel instancia
-/// al arrancar. Vive como un objeto del grafo de objetos; el superbloque
-/// guarda su hash en el campo `manifiesto`.
-#[derive(Serialize, Deserialize, Clone)]
-pub struct Manifiesto {
-    /// Version del formato — debe ser [`VERSION_MANIFIESTO`].
-    pub version: u32,
-    /// Las aplicaciones del userspace, en orden de arranque.
-    pub apps: Vec<EntradaApp>,
-}
-
-/// Una entrada del manifiesto: una aplicacion del userspace y todo lo que el
-/// kernel necesita para darle vida — su bytecode, su ventana, su cuota de
-/// memoria y, si lo tuviera, su ultimo estado persistido.
-#[derive(Serialize, Deserialize, Clone)]
-pub struct EntradaApp {
-    /// Nombre legible — para los rotulos de la consola y la baliza.
-    pub nombre: String,
-    /// Hash del objeto del grafo que contiene el bytecode WASM de la app.
-    pub bytecode: Hash,
-    /// Sub-region del framebuffer asignada a la app. Campos de ancho fijo
-    /// `u32` A PROPOSITO: esto es un formato EN DISCO. `RegionPantalla` usa
-    /// `usize` (ancho dependiente de plataforma) y no sirve para serializar.
-    pub region_x: u32,
-    pub region_y: u32,
-    pub region_ancho: u32,
-    pub region_alto: u32,
-    /// Techo de memoria lineal de la app, en bytes. Sustituye a la constante
-    /// global `wasm::TECHO_MEMORIA` — cada app lleva su cuota.
-    pub techo_memoria: u32,
-    /// Hash del ultimo estado persistido de la app (Fase 7c). `None` hasta
-    /// que la app guarde estado por primera vez.
-    pub estado: Option<Hash>,
-}
-
-impl EntradaApp {
-    /// Construye la `RegionPantalla` que el kernel entiende a partir de los
-    /// campos de ancho fijo del manifiesto.
-    pub fn region(&self) -> RegionPantalla {
-        RegionPantalla {
-            x: self.region_x as usize,
-            y: self.region_y as usize,
-            ancho: self.region_ancho as usize,
-            alto: self.region_alto as usize,
-        }
-    }
-}
-
-impl Manifiesto {
-    /// Serializa el manifiesto a su forma binaria `postcard` — la carga util
-    /// del objeto del grafo que lo aloja.
-    pub fn serializar(&self) -> Result<Vec<u8>, &'static str> {
-        postcard::to_allocvec(self).map_err(|_| "manifiesto :: serializacion fallida")
-    }
-
-    /// Reconstruye un manifiesto desde la carga util de su objeto. Rechaza
-    /// un formato de version desconocida en lugar de malinterpretarlo.
-    pub fn deserializar(bytes: &[u8]) -> Result<Manifiesto, &'static str> {
-        let (manifiesto, _) = postcard::take_from_bytes::<Manifiesto>(bytes)
-            .map_err(|_| "manifiesto :: deserializacion fallida")?;
-        if manifiesto.version != VERSION_MANIFIESTO {
-            return Err("manifiesto :: version de formato desconocida");
-        }
-        Ok(manifiesto)
+// Los tipos del manifiesto los define `formato`; se reexportan para que el
+// resto del kernel los nombre `manifiesto::EntradaApp` / `manifiesto::Manifiesto`.
+pub use formato::{EntradaApp, Manifiesto};
+
+/// Traduce la sub-region de una `EntradaApp` —campos `u32` de ancho fijo, el
+/// formato en disco— a la `RegionPantalla` que el kernel entiende (`usize`,
+/// ancho de plataforma). El puente entre lo que el disco guarda y lo que el
+/// compositor dibuja.
+pub fn region(entrada: &EntradaApp) -> RegionPantalla {
+    RegionPantalla {
+        x: entrada.region_x as usize,
+        y: entrada.region_y as usize,
+        ancho: entrada.region_ancho as usize,
+        alto: entrada.region_alto as usize,
     }
 }
 
 /// Lee el manifiesto del grafo: toma su hash del ancla del superbloque,
-/// recupera el objeto y lo deserializa. `Ok(None)` si el disco aun no tiene
-/// manifiesto anclado — el caller debe entonces sembrar la genesis.
+/// recupera el objeto y lo deserializa. `Ok(None)` si el disco no tiene
+/// manifiesto anclado —un disco que `boot` no sembro—; el kernel se levanta
+/// entonces sin userspace, pero se levanta.
 pub fn cargar() -> Result<Option<Manifiesto>, &'static str> {
-    let hash = match crate::almacen::manifiesto() {
+    let hash = match almacen::manifiesto() {
         Some(hash) => hash,
         None => return Ok(None),
     };
     // `recuperar` recomputa el hash del objeto y verifica su integridad: un
     // manifiesto corrupto se delata aqui.
-    let objeto = crate::almacen::recuperar(&hash)?
+    let objeto = almacen::recuperar(&hash)?
         .ok_or("manifiesto :: el objeto anclado no existe en el grafo")?;
     let manifiesto = Manifiesto::deserializar(&objeto.datos)?;
     Ok(Some(manifiesto))
 }
-
-// =============================================================================
-//  La genesis — la semilla transitoria de la Fase 7a
-// -----------------------------------------------------------------------------
-//  El bytecode de las apps de genesis viaja, POR AHORA, empotrado en el kernel.
-//  Es el unico `include_bytes!` que sobrevive a la Fase 7a — y solo como
-//  semilla: en un disco virgen, `sembrar_genesis` lo graba en el grafo una vez.
-//  La Fase 7b lo movera al constructor de imagen `boot` y este bloque morira.
-// =============================================================================
-
-static APP_WASM: &[u8] = include_bytes!("../assets/app.wasm");
-static DISCOLA_WASM: &[u8] = include_bytes!("../assets/discola.wasm");
-static GLOTONA_WASM: &[u8] = include_bytes!("../assets/glotona.wasm");
-static CRONISTA_WASM: &[u8] = include_bytes!("../assets/cronista.wasm");
-
-/// Descriptor de una app de genesis: lo que el kernel sabe de ella ANTES de
-/// que exista en el grafo. `region` es `(x, y, ancho, alto)` en pixeles.
-struct AppGenesis {
-    nombre: &'static str,
-    bytecode: &'static [u8],
-    region: (u32, u32, u32, u32),
-    techo_memoria: u32,
-}
-
-/// El userspace de genesis: las cinco aplicaciones que pueblan un disco
-/// virgen, con las regiones de la Fase 6.2. `app.wasm` aparece dos veces
-/// —dos instancias del mismo bytecode—; el grafo, direccionado por contenido,
-/// lo guarda una sola vez.
-fn genesis() -> [AppGenesis; 5] {
-    let techo = crate::wasm::TECHO_MEMORIA as u32;
-    [
-        AppGenesis {
-            nombre: "hola-izq",
-            bytecode: APP_WASM,
-            region: (100, 120, 480, 560),
-            techo_memoria: techo,
-        },
-        AppGenesis {
-            nombre: "hola-der",
-            bytecode: APP_WASM,
-            region: (700, 120, 480, 560),
-            techo_memoria: techo,
-        },
-        AppGenesis {
-            nombre: "discola",
-            bytecode: DISCOLA_WASM,
-            region: (60, 700, 360, 80),
-            techo_memoria: techo,
-        },
-        AppGenesis {
-            nombre: "glotona",
-            bytecode: GLOTONA_WASM,
-            region: (460, 700, 360, 80),
-            techo_memoria: techo,
-        },
-        AppGenesis {
-            nombre: "cronista",
-            bytecode: CRONISTA_WASM,
-            region: (860, 700, 360, 80),
-            techo_memoria: techo,
-        },
-    ]
-}
-
-/// Siembra el grafo en un disco sin manifiesto: graba el bytecode de cada app
-/// de genesis como un objeto, compone un `Manifiesto` con sus regiones y
-/// cuotas, lo graba —con las aristas hacia los objetos de bytecode— y lo
-/// ancla en el superbloque. Devuelve el hash del manifiesto recien anclado.
-pub fn sembrar_genesis() -> Result<Hash, &'static str> {
-    let mut apps: Vec<EntradaApp> = Vec::new();
-    let mut hijos: Vec<Hash> = Vec::new();
-
-    for app in genesis() {
-        // Grabar el bytecode como objeto del grafo. Idempotente: dos
-        // instancias de la misma app comparten un unico objeto.
-        let bytecode = crate::almacen::almacenar(app.bytecode.to_vec(), Vec::new())?;
-        if !hijos.contains(&bytecode) {
-            hijos.push(bytecode);
-        }
-        let (x, y, ancho, alto) = app.region;
-        apps.push(EntradaApp {
-            nombre: String::from(app.nombre),
-            bytecode,
-            region_x: x,
-            region_y: y,
-            region_ancho: ancho,
-            region_alto: alto,
-            techo_memoria: app.techo_memoria,
-            estado: None,
-        });
-    }
-
-    // El objeto del manifiesto: sus `hijos` son los objetos de bytecode, de
-    // modo que el grafo lo lea como el nodo padre del userspace.
-    let manifiesto = Manifiesto {
-        version: VERSION_MANIFIESTO,
-        apps,
-    };
-    let bytes = manifiesto.serializar()?;
-    let hash = crate::almacen::almacenar(bytes, hijos)?;
-    crate::almacen::fijar_manifiesto(hash)?;
-    Ok(hash)
-}
diff --git a/renaser/kernel/src/wasm/mod.rs b/renaser/kernel/src/wasm/mod.rs
index 318762a..75e4ad1 100644
--- a/renaser/kernel/src/wasm/mod.rs
+++ b/renaser/kernel/src/wasm/mod.rs
@@ -32,15 +32,6 @@ const FUEL_ARRANQUE: u64 = 20_000_000;
 /// milisegundos y es desalojada. Este numero ES el techo temporal del userspace.
 const FUEL_FOTOGRAMA: u64 = 2_000_000;
 
-/// Techo de memoria lineal por aplicacion: 4 MiB. Un modulo que intente crecer
-/// su memoria mas alla es desalojado — el aislamiento ESPACIAL del userspace,
-/// gemelo del techo TEMPORAL que impone el combustible.
-///
-/// Desde la Fase 7 el techo es POR-APP: cada `EntradaApp` del manifiesto
-/// lleva el suyo. Esta constante es el valor por DEFECTO — el que usan las
-/// apps de genesis (ver `manifiesto::genesis`).
-pub(crate) const TECHO_MEMORIA: usize = 4 * 1024 * 1024;
-
 /// Por que el kernel da por terminada —desaloja— una aplicacion WASM.
 #[derive(Clone, Copy)]
 pub enum FallaApp {