1# Registro de cambios — renaser
2
3Registro técnico detallado, fase a fase, en orden cronológico. Formato
4inspirado en *Keep a Changelog*. Para la crónica en lenguaje llano, ver
5`DIARIO.md`; para el estado y los planes, `ROADMAP.md`.
6
7---
8
9## Fase 1 — El primer microsegundo — 2026-05-21
10
11### Infraestructura
12- Espacio de trabajo Cargo, `kernel/Cargo.toml`, `.cargo/config.toml`,
13 `rust-toolchain.toml`.
14- Target inicial: especificación JSON propia `x86_64-renaser.json` — PIC,
15 soft-float, sin SSE, enlazador `lld`.
16
17### Añadido
18- `kernel/src/main.rs`: punto de entrada con el macro `entry_point!` de
19 `bootloader_api` 0.11.
20- Adopción y verificación con seguridad de tipos del framebuffer GOP (ancho,
21 alto, formato de píxel, *stride*).
22- Lienzo intermedio estático de 8 MiB en `.bss`, alineado a página: la técnica
23 de doble búfer para evitar parpadeos.
24- `#[panic_handler]` que dibuja una franja roja directamente sobre el
25 framebuffer mediante escrituras volátiles.
26- Dependencias: `bootloader_api` 0.11, `x86_64` 0.15, `embedded-graphics` 0.8.
27
28### Notas
29- Corrección clave: `bootloader` (constructor de imagen, lado anfitrión) no es
30 `bootloader_api` (la API `no_std` que consume el kernel).
31
32---
33
34## Fase 1.5 — Empaquetado y arranque — 2026-05-21
35
36### Cambiado
37- Migración del target del kernel: de la JSON propia `x86_64-renaser.json` al
38 target nativo precompilado `x86_64-unknown-none`. Elimina `build-std` y la
39 burocracia de compilador asociada.
40
41### Añadido
42- Miembro `boot/`: orquestador de anfitrión. Dependencia de artefacto
43 (RFC 3028) sobre el kernel; construcción de la imagen de disco UEFI con
44 `bootloader::UefiBoot`; lanzador de QEMU.
45
46### Toolchain
47- Instalación de la toolchain nightly (la máquina solo tenía la estable).
48
49### Corregido
50- Los nightly recientes exigen `-Zjson-target-spec` para usar specs JSON
51 (resuelto al migrar al target nativo).
52- Sintaxis del acelerador de QEMU: `-machine q35,accel=kvm:tcg`.
53
54### Verificado
55- Arranque en QEMU: superficie índigo limpia a 1280×800.
56- Baliza de pánico (franja roja) confirmada inyectando un pánico de prueba.
57
58---
59
60## Fase 2.0 — Cimientos del manejo de fallos — 2026-05-21
61
62### Añadido
63- `kernel/src/gdt.rs`: GDT propia, TSS y un stack de emergencia (IST)
64 reservado para el manejador de doble fallo.
65- `kernel/src/interrupts.rs`: IDT con manejadores de excepción de CPU. El
66 breakpoint (#BP) es recuperable; #UD, #DE, #GP, #PF y #DF son fatales y
67 encienden la baliza.
68- `#![feature(abi_x86_interrupt)]`.
69
70### Corregido
71- La GDT debe recargar los registros SS/DS/ES con un segmento de datos del
72 kernel: el cargador deja `SS = 0x10`, valor que en la GDT nueva pasa a ser el
73 descriptor del TSS; el primer `iretq` de una rutina de excepción provocaba un
74 #GP. Diagnosticado con la traza `-d int` de QEMU.
75
76### Verificado
77- Excepción `int3` atrapada y superada; línea-latido teal dibujada como prueba.
78
79---
80
81## Fase 2.1 — Interrupciones de hardware — 2026-05-21
82
83### Añadido
84- `kernel/src/pic.rs`: remapeo del par 8259 (PIC) fuera del rango de las
85 excepciones (vectores 0x20+); programación del temporizador PIT a 100 Hz;
86 IRQ1 de teclado.
87- Bucle de render en `kernel_main`, despertado por el temporizador, en
88 sustitución del `hlt` estático.
89
90### Verificado
91- La línea-latido se anima (temporizador en marcha); las pulsaciones de teclado
92 inyectadas cambian el cuadro-eco (teclado en marcha).
93
94---
95
96## Fase 3 — Memoria dinámica y reactor asíncrono — 2026-05-21
97
98### Añadido
99- `kernel/src/memory/`: `linked_list_allocator` como `#[global_allocator]`
100 sobre una región estática en `.bss`.
101- `#[alloc_error_handler]`: franja naranja de agotamiento de memoria.
102 `#![feature(alloc_error_handler)]`.
103- `kernel/src/async_system/`: reactor cooperativo — `Executor`, `Task`/`TaskId`,
104 `Waker` (vía `alloc::task::Wake`) y `ScancodeStream`.
105- `kernel/src/texto.rs`: rasterización de tipografía vectorial con `fontdue`;
106 TTF Adwaita Mono empotrada con `include_bytes!`.
107- Dependencias: `linked_list_allocator` 0.10, `spin` 0.9, `crossbeam-queue`
108 0.3, `futures-util` 0.3, `fontdue` 0.9.
109
110### Cambiado
111- Heap ampliado de 16 MiB a 64 MiB: `fontdue`, al analizar una tipografía real,
112 agotaba los 16 MiB iniciales. El `#[alloc_error_handler]` lo delató pintando
113 la franja naranja — el guardarraíl funcionó en silicio real.
114
115### Corregido
116- El modo `no_std` de `fontdue` está condicionado a su feature `hashbrown`; sin
117 ella recae en `std`. Se fija `default-features = false, features = ["hashbrown"]`.
118
119### Verificado
120- Rótulo de bienvenida rasterizado al arranque; el texto tecleado aparece en vivo.
121
122---
123
124## Fase 4 — El escudo de aislamiento WASM — 2026-05-21
125
126### Añadido
127- `kernel/src/wasm/`: intérprete `wasmi` 1.0.9 en modo `no_std`. `env.rs`
128 define la matriz de capacidades — exactamente dos funciones de host:
129 `sys_render_frame(ptr, len)`, con validación infranqueable de los límites de
130 la memoria lineal, y `sys_get_scancode()`.
131- `apps/hello_wasm/`: primera aplicación del userspace — módulo
132 `wasm32-unknown-unknown` (cdylib), un cuadrado móvil dirigido por teclado. Se
133 empotra en el kernel con `include_bytes!`.
134- Dependencia: `wasmi` 1.0.
135
136### Cambiado
137- El kernel se EXCLUYE del espacio de trabajo (`exclude = ["kernel", "apps"]`):
138 al añadir `wasmi`, `cargo build` intentaba compilar el kernel para el
139 anfitrión y fallaba por un símbolo `_start` duplicado. El kernel pasa a
140 construirse únicamente como dependencia de artefacto de `boot`.
141
142### Corregido
143- `wasmi` `no_std` necesita `default-features = false, features = ["hash-collections"]`.
144- `wasmi` 1.0.9 usa `Linker::instantiate_and_start` (no existe `instantiate`).
145
146### Verificado
147- La aplicación WASM pinta su propia superficie y responde a las pulsaciones de
148 teclado inyectadas, sin más vía hacia el kernel que las dos capacidades.
149
150---
151
152## Mantenimiento — Estructura, documentación e integración — 2026-05-21
153
154### Cambiado
155- Refactorización: `main.rs` dividido de 692 a ~155 líneas. Se extraen los
156 módulos `sync.rs`, `grafico.rs`, `consola.rs` y `baliza.rs`. Sin cambios de
157 comportamiento (verificado en QEMU).
158
159### Añadido
160- Documentación: `CLAUDE.md`, `README.md`, `ARCHITECTURE.md`, `ROADMAP.md`,
161 `CHANGELOG.md` y `DIARIO.md`.
162- Integración con git: repositorio inicializado, remoto `origin` en Gitea,
163 `.gitignore`.
164
165### Seguridad
166- `renaser.txt` —un borrador de trabajo que contenía una credencial— se subió
167 por error en el commit inicial. Se purgó de todo el historial con
168 `git filter-branch` y se reescribió el remoto con `push --force`. La
169 credencial expuesta debe rotarse.
170
171---
172
173## Fase 5 — Multitarea cooperativa, guardarrail de fuel y reloj — 2026-05-22
174
175Unificación de la Fase 3 (reactor) y la Fase 4 (WASM): el userspace deja de ser
176una sola app que monopoliza la CPU y pasa a ser un conjunto de aplicaciones
177cooperativas, aisladas también en el TIEMPO.
178
179### Añadido
180- `kernel/src/async_system/reloj.rs`: convierte la IRQ0 (PIT, 100 Hz) en una
181 primitiva asíncrona. `CONTADOR_PULSOS` (`AtomicU64`) y `EsperaFrame`, un
182 `Future` que se resuelve en el siguiente pulso — la unidad de cesión
183 cooperativa del userspace. Censo de wakers tras `Mutex`, drenado por la IRQ0.
184- `kernel/src/wasm`: ABI de fotograma. `AplicacionWasm` —instancia PERSISTENTE
185 entre fotogramas (`Store` + `TypedFunc<(), ()>` + región)— sustituye al
186 `wasm::ejecutar` de fuego-y-olvido. El módulo del userspace exporta ahora
187 `init()` (una vez) y `tick()` (un fotograma, y retorna).
188- Escudo de combustible: `Engine` con `Config::consume_fuel(true)` y
189 `CompilationMode::Eager` (el `fuel` mide solo ejecución). Presupuesto recargado
190 antes de cada `tick` — `FUEL_ARRANQUE` (20 M) y `FUEL_FOTOGRAMA` (2 M).
191- `kernel/src/wasm/env.rs`: capacidad `sys_render_frame` con **regiones de
192 dibujo** — cada fotograma se compone desplazado por el `(offset_x, offset_y)`
193 de la app; un tamaño ajeno a la región se rechaza. Canal de teclado **por
194 aplicación**: la IRQ1 difunde cada scancode a TODOS los canales, de modo que
195 varias apps reciben la entrada en paralelo sin robársela.
196- `kernel/src/grafico.rs`: `RegionPantalla` y `Color::DESALOJO` (púrpura).
197- `apps/discola/`: aplicación WASM construida para portarse mal — su `tick` es
198 un bucle cerrado. Demuestra el guardarrail de fuel en vivo.
199
200### Cambiado
201- `apps/hello_wasm`: migrada al ABI `init`/`tick`; su lienzo se dimensiona a la
202 región (480×560). El estado del cuadrado persiste en su memoria lineal.
203- `interrupts.rs`: la IRQ0 avanza el `reloj`; la IRQ1 difunde a los canales.
204- `consola.rs`: `volcar_marco` compone en una sub-región; `pintar_desalojo`
205 tatúa la baliza púrpura.
206- Contención de fallos: el subsistema WASM ya no usa `.expect()`. Toda falla
207 —carga, instanciación, desbordamiento, agotamiento de fuel— se devuelve como
208 `Result`; la tarea desaloja la app (baliza púrpura) y el kernel sigue vivo.
209
210### Verificado
211- QEMU: tres apps concurrentes. Dos instancias de `hello_wasm` (mismo bytecode,
212 regiones izquierda y derecha) renderizan y responden a W/A/S/D **en paralelo**,
213 con movimiento idéntico. La app díscola es desalojada en su primer fotograma
214 por agotamiento de combustible: su región queda púrpura y el sistema —kernel y
215 apps vecinas— no sufre un solo sobresalto.
216
217---
218
219## Fase 6.0 — Cuotas de memoria y ciclo de vida del userspace — 2026-05-22
220
221El aislamiento de las aplicaciones, que la Fase 5 hizo temporal (combustible),
222se completa ahora en la dimensión espacial (memoria) y se cierra la fuga de
223recursos del ciclo de vida.
224
225### Añadido
226- Techo de memoria lineal por aplicación: `4 MiB`. `wasm/mod.rs` construye un
227 `StoreLimits` (`StoreLimitsBuilder::memory_size` + `trap_on_grow_failure`) y lo
228 liga al `Store` con `Store::limiter`. Un `memory.grow` que rebase la cuota se
229 convierte en trampa; el kernel la captura y desaloja la app.
230- `FallaApp::SinMemoria` y `Color::DESALOJO_MEMORIA` (amarillo pálido). El
231 desalojo distingue la causa por color: púrpura (tiempo/aborto), amarillo
232 (memoria). La clasificación es robusta — `Error::as_trap_code()` da un código
233 público y unívoco: `TrapCode::GrowthOperationLimited`.
234- `Drop` para `AplicacionWasm`: al morir una app desalojada, su canal de teclado
235 se da de baja del censo de difusión de la IRQ1 — cierra la fuga señalada en la
236 Fase 5. `async_system::teclado` se reorganiza en `crear_canal` /
237 `registrar_canal` / `cerrar_canal`; el canal se inscribe al FINAL de la carga,
238 de modo que una carga fallida no deja canales huérfanos.
239- `apps/glotona/`: aplicación WASM construida para devorar memoria — su `tick`
240 invoca `memory.grow` sin freno. Demuestra el guardarrail espacial en vivo.
241
242### Verificado
243- QEMU: cuatro apps concurrentes. Las dos `hello_wasm` renderizan y responden al
244 teclado en paralelo; la app díscola es desalojada por combustible (franja
245 púrpura) y la app glotona por cuota de memoria (franja amarilla), ambas en su
246 primer fotograma, sin que el kernel ni las apps honradas se inmuten.
247
248---
249
250## Fase 6.1a — Sonda PCI y disco de pruebas — 2026-05-22
251
252Primer paso de la estrategia incremental hacia el almacenamiento: derribar el
253muro del descubrimiento de hardware antes de diseñar nada encima.
254
255### Añadido
256- `kernel/src/drivers/`: nuevo subsistema de drivers. `pci.rs` enumera el bus
257 PCI por fuerza bruta mediante el mecanismo de configuración #1 —puertos de E/S
258 `0xCF8` (dirección) y `0xCFC` (datos)—; recorre buses y dispositivos leyendo
259 el Vendor/Device ID, y localiza el disco virtio-blk (vendor `0x1AF4`, device
260 `0x1001`/`0x1042`), devolviendo su ubicación y sus seis BARs.
261- `kernel_main`: tras fundar la consola, sondea el bus PCI y deja constancia
262 visual del hallazgo.
263
264### Cambiado
265- `boot/src/main.rs`: forja un disco de pruebas `target/disk.img` (fichero
266 disperso de 32 MiB; se respeta si ya existe) y lo adjunta a QEMU como
267 `virtio-blk-pci`. **Corrección de plataforma:** la máquina `q35` es x86_64 y
268 exige la transmisión PCI; `virtio-blk-device` (su gemelo MMIO) es de ARM.
269
270### Verificado
271- QEMU: el kernel enumera el bus y reporta en pantalla
272 `virtio-blk en bus 0 dev 3 :: BAR0 E/S 0x6000` — un dispositivo virtio-blk
273 transicional (device `0x1001`), con su BAR0 en espacio de E/S. El muro del
274 descubrimiento PCI queda derribado; las cuatro apps del userspace siguen
275 operando sin alteración.
276
277---
278
279## Fase 6.1b — HAL, DMA y lectura del sector 0 — 2026-05-22
280
281Segundo paso del sustrato de almacenamiento: el diálogo real con el disco. El
282kernel monta el dispositivo virtio-blk y lee su primer sector por DMA.
283
284### Añadido
285- Dependencia `virtio-drivers` 1.13 (`no_std`, `default-features = false`,
286 feature `alloc`).
287- `kernel/src/drivers/disco.rs`:
288 - **Asignador de marcos** «bump» — reparte páginas físicas de 4 KiB para el
289 DMA, tomadas de la mayor región de RAM libre que el cargador reporta. No
290 libera: suficiente para una sonda (la gestión fina llegará con el grafo).
291 - **`KernelHal`** — implementa el `trait Hal` de `virtio-drivers`. `dma_alloc`
292 entrega marcos físicos a cero; `mmio_phys_to_virt` traduce los BARs (el
293 cargador mapea ≥ 4 GiB de memoria física, que cubre todo MMIO de PCI);
294 `share`/`unshare` usan un buffer rebote para que cualquier región del kernel
295 pueda viajar al dispositivo.
296 - **`montar_y_leer_sector0`** — enumera el bus, habilita E/S + memoria +
297 bus-master, monta el `PciTransport` y el `VirtIOBlk`, y lee el sector 0 por
298 **sondeo** del *used ring* (sin depender aún de interrupciones).
299- `kernel/src/drivers/pci.rs`: reescrito como `CamPuertos`, la implementación de
300 `ConfigurationAccess` de `virtio-drivers` sobre los puertos `0xCF8`/`0xCFC`.
301- `kernel_main` captura `physical_memory_offset` y la región de RAM de
302 `BootInfo`, funda el subsistema de disco y reporta el resultado de la sonda.
303
304### Cambiado
305- `boot/src/main.rs`: al forjar el disco de pruebas graba una firma
306 (`renaser-6.1b`) en su sector 0 — el testigo del viaje de ida y vuelta.
307
308### Verificado
309- QEMU: el kernel reporta `virtio-blk :: bus 0 dev 3 :: 65536 sectores ::
310 s0=renaser-6.1b`. La firma grabada por el anfitrión se lee de vuelta intacta:
311 descubrimiento PCI, transporte, DMA y transferencia funcionan de punta a
312 punta. Las cuatro apps del userspace siguen operando sin alteración.
313
314---
315
316## Fase 6.1c — El grafo de objetos direccionado por contenido — 2026-05-22
317
318Tercer y último paso del sustrato de almacenamiento. El disco deja de ser un
319dispositivo que se sondea y pasa a ser una MEMORIA QUE PERDURA: un grafo
320dirigido acíclico de objetos direccionados por contenido — no un sistema de
321archivos plano POSIX.
322
323### Añadido
324- `kernel/src/almacen.rs` — el grafo de objetos:
325 - **Objeto** — una carga útil de bytes (`datos`) y una lista de aristas
326 (`hijos`: hashes de otros objetos). Las aristas hacen del almacén un DAG.
327 - **Direccionamiento por contenido** — la identidad de un objeto es el hash
328 BLAKE3 de su forma serializada. De ello se siguen dos propiedades que un
329 sistema de archivos jamás regala: INTEGRIDAD (el contenido leído se
330 rehashea y se verifica contra el hash pedido) y DEDUPLICACIÓN (contenido
331 idéntico produce el mismo hash; se almacena una sola vez).
332 - **Disco como log** — el sector 0 es el superbloque (magia `RENASGRF`,
333 versión, cursor del log y hash de la raíz); tras él se anexan los registros
334 de objetos, `[longitud u32 LE][payload postcard][relleno a cero]`. Un
335 índice en memoria (hash -> sector) se reconstruye al arrancar recorriendo
336 el log de cabo a rabo.
337 - API: `init` —monta el disco, lee o forja el superbloque, reconstruye el
338 índice—, `almacenar`, `recuperar`, `raiz` y `fijar_raiz`.
339- Cinco capacidades nuevas del host en `wasm/env.rs` — `sys_object_put`,
340 `sys_object_datos`, `sys_object_hijo`, `sys_object_raiz` y
341 `sys_object_fijar_raiz` —, con la misma validación infranqueable de límites
342 de la memoria lineal que `sys_render_frame`. Distinguen dos clases de fallo:
343 un puntero inválido ABORTA la app —es su culpa, se traduce en trampa—; un
344 fallo del almacenamiento le devuelve un código de error negativo —no lo es—.
345- `apps/cronista/` — la primera aplicación del userspace que escribe en el
346 almacenamiento PERSISTENTE. En cada arranque consulta la raíz del grafo,
347 graba un objeto nuevo —`datos`: el número de arranque; `hijos`: la raíz
348 anterior, el eslabón del DAG— y lo corona como raíz. Pinta una celda por
349 arranque registrado y un testigo de integridad que recorre la cadena entera.
350- Dependencias: `serde` 1 y `postcard` 1 (serialización binaria compacta, el
351 formato que viaja al disco) y `blake3` 1 (la función hash). Las tres `no_std`.
352
353### Cambiado
354- `kernel/src/drivers/disco.rs` reescrito para un sustrato PERMANENTE:
355 - El asignador de marcos «bump» de la Fase 6.1b cede el paso a uno de MAPA DE
356 BITS con liberación real. `dma_dealloc` y `unshare` devuelven los marcos a
357 la arena: un almacén vivo, con su trasiego incesante de DMA, ya no la agota.
358 - El `VirtIOBlk` deja de montarse y destruirse en cada llamada: se monta UNA
359 vez y queda tras un `Mutex` global. `leer_sectores` / `escribir_sectores`
360 exponen la E/S de bloques — el disco deja de ser de solo lectura. Se retira
361 `montar_y_leer_sector0`, la sonda de un solo uso de la Fase 6.1b.
362- `boot/src/main.rs`: el disco de pruebas pasa a ser el disco de objetos. Ya
363 no se le graba una firma — se forja virgen, a cero, y el kernel lo formatea
364 la primera vez que no halle el superbloque.
365- `main.rs`: `informar_disco` (la sonda de la Fase 6.1b) se sustituye por
366 `informar_almacen`, que funda el grafo. El userspace pasa de cuatro a cinco
367 apps; las regiones de discola y glotona se reajustan para alojar a cronista.
368
369### Notas
370- **blake3 forzado a escalar.** El target del kernel corre sin SSE; un camino
371 SIMD de blake3 activado por detección en tiempo de ejecución ejecutaría
372 instrucciones que la CPU, sin `CR4.OSFXSR`, rechazaría con un #UD. Se fija
373 blake3 con `pure` + los cuatro `no_*` (`sse2`, `sse41`, `avx2`, `avx512`):
374 implementación puramente escalar, sin SIMD ni ensamblador.
375
376### Verificado
377- QEMU, TRES arranques consecutivos sobre el mismo disco persistente: la app
378 cronista pinta 1, luego 2, luego 3 celdas — la cuenta de arranques sobrevive
379 a los reinicios porque vive en el grafo, en el disco, no en la RAM. El
380 testigo de integridad queda VERDE en los tres: el DAG entero se recorre, de
381 la raíz al primer eslabón, y su profundidad cuadra con la cuenta. El
382 superbloque en disco confirma la magia `RENASGRF`, versión 1, cursor 4 y la
383 raíz del tercer arranque; el registro del primer arranque guarda `datos = 1`
384 y cero hijos. Las otras cuatro apps siguen su curso sin alteración — las dos
385 `hello_wasm` renderizando, discola desalojada en púrpura, glotona en amarillo.
386
387---
388
389## Fase 6.2 — E/S de disco asíncrona por interrupción — 2026-05-22
390
391La Fase 6.1 hizo hablar al disco, pero por SONDEO: el procesador se quedaba en
392espera activa vigilando el *used ring* de virtio, incapaz de atender nada más.
393La 6.2 libera el planificador cooperativo — la E/S de bloques pasa a ser
394REACTIVA, guiada por la interrupción física del dispositivo.
395
396### Añadido
397- `EsperaDisco` (`drivers/disco.rs`): una transferencia de bloques expresada
398 como `Future` nativo. Posee sus buferes DMA —`BlkReq`, `BlkResp` y los datos,
399 en el heap para una dirección estable—; su `poll` envía la petición por la
400 API NO BLOQUEANTE de `virtio-drivers` (`read_blocks_nb`/`write_blocks_nb`),
401 consulta el *used ring* (`peek_used`) y, si la transferencia sigue en vuelo,
402 inscribe el waker y cede. `leer_bloques`/`escribir_bloques` lo construyen.
403- La IRQ del disco: `montar` descubre la línea de IRQ legada del dispositivo
404 (registro «Interrupt Line», offset 0x3C del espacio de configuración PCI),
405 registra un manejador en la IDT y abre la línea en el PIC.
406 `interrupts::irq_disco` → `disco::atender_irq` reconoce la interrupción en el
407 dispositivo —leer su registro ISR baja la línea INTx— y despierta, vía un
408 waker de ranura única, a la tarea que aguardaba el bloque.
409- `bloquear_en` — el puente para los contextos SÍNCRONOS (el arranque, las
410 capacidades WASM, que no pueden `.await`): lleva un `Future` de disco hasta
411 su final durmiendo la CPU con `hlt` —la despiertan la IRQ del disco o el
412 temporizador, como red de seguridad—; jamás en espera activa con el sistema
413 ya en marcha.
414- `pic::desenmascarar` / `pic::vector_irq`: abrir una línea concreta del par
415 8259 (con su cascada, si vive en el esclavo) y mapear línea → vector de IDT.
416- `pci::linea_irq`: leer el registro «Interrupt Line» de un dispositivo.
417- `tarea_sonda_disco` (`main.rs`): una tarea del reactor que lee el sector 0 de
418 forma asíncrona — la prueba viva de que la IRQ conduce la E/S sin detener a
419 las aplicaciones.
420
421### Cambiado
422- `leer_sectores`/`escribir_sectores` se reescriben sobre la maquinaria
423 asíncrona (`bloquear_en` + `EsperaDisco`). El `almacen` y las capacidades
424 `sys_object_*` NO cambian una línea: heredan la E/S por interrupción de
425 forma transparente — la espera de disco deja de quemar ciclos en sondeo.
426- El `VirtIOBlk` pide al dispositivo que emita interrupciones al completar cada
427 petición (`enable_interrupts`).
428
429### Decisiones de ingeniería
430- **PIC, no IOAPIC.** El kernel corre íntegramente sobre el par 8259 (`pic.rs`)
431 y la crate `x86_64` no ofrece abstracción de APIC. Las IRQ legadas de PCI de
432 la máquina `q35` se enrutan por el 8259: basta leer la línea que el firmware
433 UEFI ya asignó y abrirla. Migrar al IOAPIC habría exigido levantar LAPIC +
434 IOAPIC + parseo de tablas ACPI — un subsistema entero, desproporcionado para
435 el objetivo. El resultado funcional es idéntico: E/S conducida por la
436 interrupción física.
437- **Las capacidades WASM no `.await`-ean.** El intérprete `wasmi` ejecuta las
438 funciones de host de forma SÍNCRONA: no hay manera de suspender un módulo a
439 mitad de una llamada de host. Por eso `sys_object_*` no se vuelven
440 asíncronas; usan `bloquear_en`, que duerme la CPU con `hlt` en lugar de
441 sondear. El verdadero solapamiento E/S ↔ render lo aprovechan las TAREAS del
442 reactor (`tarea_sonda_disco` hoy; la carga dinámica de módulos, mañana).
443- Una IRQ legada de PCI es de NIVEL: el manejador reconoce primero al
444 dispositivo —lo que baja su línea— y sólo después cierra el EOI del PIC; el
445 orden inverso reavivaría la interrupción en bucle.
446- Todo acceso al disco toma su `Mutex` con las interrupciones desactivadas
447 (`without_interrupts`): la IRQ del disco jamás encuentra el cerrojo ocupado,
448 lo que hace imposible el interbloqueo por interrupción.
449
450### Verificado
451- QEMU: el disco virtio-blk se enruta a la IRQ 11. La `tarea_sonda_disco`
452 reporta «sonda asíncrona OK -- 2 IRQ de disco atendidas»: la interrupción del
453 disco dispara de verdad. La app cronista, sobre el disco persistente heredado
454 de la Fase 6.1c, continúa la cuenta de arranques (3 → 4 → 5 celdas) con el
455 testigo de integridad del DAG en verde — la nueva E/S asíncrona lee y escribe
456 el grafo sin un solo fallo. Las cuatro apps WASM siguen su curso, sin un
457 sobresalto ni un micro-congelamiento.
458
459## Fase 7 — Apertura: Apps que nacen del Grafo — 2026-05-22
460
461Apertura de la Fase 7. Este commit asienta el plan y el andamiaje; **no
462cambia el comportamiento del kernel**. La Fase 7 destierra el
463`include_bytes!` del userspace: las aplicaciones pasan a ser objetos del
464grafo, y un Manifiesto de Génesis —también en el grafo— dicta qué arranca,
465con qué cuota y en qué región.
466
467### Añadido
468- `FASE7.md` — el plan de ataque: el objetivo, el problema huevo-y-gallina
469 de la génesis, las sub-fases 7a (manifiesto + carga desde el grafo, con
470 semilla por el kernel), 7b (siembra de la imagen por `boot`, muerte del
471 `include_bytes!`) y 7c (persistencia inter-sesión), y los guardarraíles.
472- `kernel/src/manifiesto.rs` — andamiaje del Manifiesto de Génesis. Los
473 tipos `Manifiesto` / `EntradaApp` y la (de)serialización `postcard` están
474 completos; `cargar` y `sembrar_genesis` son esbozos hasta la Fase 7a.
475 La sub-región se guarda en campos `u32` de ancho fijo (formato en disco),
476 no como `RegionPantalla` (`usize`, ancho de plataforma).
477
478### Notas
479- El módulo se declara en `main.rs` pero aún no se cablea a `kernel_main`
480 (`#![allow(dead_code)]` temporal, hasta que la 7a le dé un llamador). El
481 kernel compila y se comporta idéntico a la Fase 6.2 — nada observable que
482 verificar en QEMU en esta apertura.
483
484## Fase 7a — Apps que nacen del Grafo: el Manifiesto — 2026-05-22
485
486El kernel deja de empotrar el userspace. Las cinco aplicaciones ya no llegan
487por `include_bytes!` en `main.rs`: nacen del grafo de objetos, gobernadas por
488un Manifiesto de Génesis que también vive en el grafo.
489
490### Añadido
491- `kernel/src/manifiesto.rs` — el Manifiesto de Génesis implementado: tipos
492 `Manifiesto` / `EntradaApp`, (de)serialización `postcard`, `cargar` (lee el
493 manifiesto del grafo vía el ancla del superbloque) y `sembrar_genesis`
494 (puebla un disco virgen con las cinco apps de génesis).
495- `almacen`: el `SuperBloque` gana el ancla `manifiesto: Option<Hash>` —
496 gemela de `raiz`, pero del lado del kernel. Accesores `manifiesto()` y
497 `fijar_manifiesto()`.
498
499### Cambiado
500- `almacen::VERSION` 1 → 2: el superbloque cambia de forma. Un disco v1 se
501 reformatea al arrancar, como cualquier disco ajeno — la cuenta de la
502 cronista reinicia una vez.
503- `kernel_main`: las cinco llamadas `encender_app` escritas a mano se
504 sustituyen por `cargar_userspace`, que carga el manifiesto —o lo siembra si
505 el disco está virgen— e itera sus `EntradaApp`.
506- `encender_app` recibe una `EntradaApp` y recupera el bytecode del grafo.
507 `recuperar` ya recomputa y verifica el hash: un bytecode corrupto se niega,
508 se pinta la baliza de desalojo en su región y el arranque continúa.
509- `wasm::AplicacionWasm::cargar` recibe el techo de memoria por-app (del
510 manifiesto) en vez de la constante global `TECHO_MEMORIA`.
511
512### Notas
513- La siembra es TRANSITORIA: el bytecode aún viaja empotrado en
514 `manifiesto.rs` (`include_bytes!`) como semilla. La Fase 7b lo moverá al
515 constructor de imagen `boot` y el `include_bytes!` morirá del todo.
516
517### Verificado
518- QEMU (captura headless por el monitor, `screendump`): los dos caminos del
519 Manifiesto confirmados.
520 - **Disco virgen** — el almacén reporta «disco formateado :: 65536 sectores
521 :: 0 objetos», la consola imprime «manifiesto :: genesis sembrada en disco
522 virgen» y «manifiesto :: 5 apps nacidas del grafo». La pantalla es
523 idéntica a la Fase 6.2: dos `hello_wasm` renderizando, discola desalojada
524 en púrpura, glotona en amarillo, cronista pintando su primera celda.
525 - **Disco ya sembrado** — sin línea de siembra: el almacén reporta «grafo
526 montado :: 6 objetos :: raíz presente» (las 5 semillas más el objeto que
527 la cronista persistió), la consola imprime sólo «manifiesto :: 5 apps
528 nacidas del grafo», y la cronista pinta su segunda celda — la cuenta de
529 arranques sobrevivió al apagón. El userspace nace del grafo en ambos
530 casos.
531
532## Fase 7b — La imagen sembrada por `boot`; muere el `include_bytes!` — 2026-05-22
533
534El kernel deja de empotrar el userspace POR COMPLETO. Hasta la 7a, el bytecode
535de las cinco apps de génesis aún viajaba dentro del binario del kernel
536(`include_bytes!`), como semilla transitoria. La 7b lo mata: es el constructor
537de imagen `boot` —en el anfitrión— quien siembra el disco con el grafo ya
538poblado. El binario del kernel ya no carga ni un solo `.wasm`.
539
540### Añadido
541- **`formato`** — nueva crate: un núcleo `#![no_std]` COMPARTIDO que define el
542 formato del grafo de objetos en disco. Tipos (`Objeto`, `SuperBloque`,
543 `Manifiesto`, `EntradaApp`), su (de)serialización `postcard`, la función hash
544 BLAKE3 (escalar pura) y el trazado de un registro del log
545 (`componer_registro` / `longitud_registro`). Lo enlazan el kernel bare-metal
546 Y el anfitrión `boot`: una sola verdad del formato, imposible de divergir
547 entre los dos lados. 5 pruebas de ida y vuelta. Excluida del workspace, como
548 el kernel.
549- `boot`: `sembrar_grafo` — siembra un disco virgen con el grafo ya poblado.
550 Graba el bytecode de cada app de génesis como un objeto (deduplicado:
551 `app.wasm`, usado dos veces, se guarda una sola), compone el Manifiesto de
552 Génesis con sus regiones y cuotas, lo graba con las aristas hacia los objetos
553 de bytecode, y forja el superbloque que lo ancla. El `.wasm` se lee de
554 `kernel/assets/` en tiempo de ejecución.
555
556### Cambiado
557- `kernel/manifiesto.rs`: pierde los cuatro `include_bytes!`, el descriptor
558 `AppGenesis`, `genesis()` y `sembrar_genesis()`. Se reduce a `cargar()` —leer
559 el manifiesto del grafo— y `region()` —traducir la región en disco a la del
560 kernel—. Los tipos `Manifiesto` / `EntradaApp` migran a `formato`.
561- `kernel/almacen.rs`: los tipos `Objeto` / `SuperBloque`, las constantes del
562 formato, el hash y el trazado de registros migran a `formato`. El módulo
563 queda como el almacén VIVO: cursor, índice y E/S contra virtio-blk.
564- `kernel_main` / `cargar_userspace`: sin la rama de siembra. Un disco sin
565 manifiesto anclado ya no se siembra desde el kernel —`boot` lo hace siempre
566 al forjar la imagen—; el kernel se levanta sin userspace y lo reporta.
567- `kernel/Cargo.toml`: deja de declarar `serde` / `postcard` / `blake3` por su
568 cuenta; los hereda —mismas features, BLAKE3 escalar puro— a través de
569 `formato`.
570- `wasm/mod.rs`: se retira la constante `TECHO_MEMORIA`, ya sin uso (el techo
571 por-app lo dicta el manifiesto; el valor de génesis vive ahora en `boot`).
572- `.cargo/config.toml`: el alias `cargo kernel` pasa de `-p kernel` a
573 `--manifest-path kernel/Cargo.toml`. Con `formato` compartido entre el kernel
574 y `boot`, pedir `-p kernel` dentro del workspace —con el kernel a la vez como
575 dependencia de artefacto— hace caer al resolvedor de features de cargo;
576 apuntar al manifiesto del kernel lo compila como raíz y esquiva el grafo.
577
578### Verificado
579- QEMU (captura headless por el monitor, `screendump`), los dos caminos:
580 - **Disco virgen** — `boot` imprime «disco de objetos sembrado :: 5 objetos,
581 manifiesto anclado»; el kernel monta el grafo de `boot` —«grafo montado ::
582 5 objetos :: raíz ausente», SIN «disco formateado» ni «génesis sembrada»—
583 e instancia las cinco apps. Pantalla idéntica a la Fase 6.2.
584 - **Segundo arranque** — `boot` respeta el disco existente («el grafo
585 perdura»); el kernel carga «6 objetos :: raíz presente» y la cronista pinta
586 su segunda celda: la persistencia sobrevive a través de un disco sembrado
587 por `boot`.
588- `cargo build -p boot` y `cargo kernel` compilan limpios; `cargo test` de
589 `formato` — 5/5 en verde.
590
591## Fase 7c — Persistencia inter-sesión: cada app recuerda lo suyo — 2026-05-22
592
593La cronista dejaba huella, pero en la RAÍZ del grafo — un ancla única que sólo
594una app puede usar. La 7c estrena la persistencia POR-APP: cada `EntradaApp`
595del Manifiesto de Génesis tiene su propia ranura `estado`, y una app guarda y
596recobra lo suyo sin pisar a nadie. El estado de cada aplicación sobrevive, por
597separado, a un reinicio.
598
599### Añadido
600- **`apps/memoriosa`** — nueva app WASM interactiva. Cuenta las teclas pulsadas
601 en toda su historia y persiste el recuento; al reiniciar, despierta con su
602 cuenta intacta. Una celda violeta por pulsación; el testigo de la esquina es
603 verde si nació limpia, ámbar si despertó con memoria de una vida anterior.
604- `kernel` — dos capacidades del host nuevas:
605 - `sys_estado_cargar(salida, capacidad)` — copia el estado persistido de la
606 app que llama en su memoria lineal; 0 si aún no tiene estado previo.
607 - `sys_estado_guardar(datos, datos_len)` — graba `datos` como objeto del
608 grafo y ancla su hash en la `EntradaApp` de la app, re-grabando y
609 re-anclando el manifiesto.
610- `manifiesto.rs` — el manifiesto VIVO: `VIVO`, un `Mutex<Manifiesto>` que el
611 kernel custodia. `instalar` (en el arranque), `estado_de` (lee la ranura de
612 una app) y `fijar_estado` (la muta, re-graba el manifiesto y lo re-ancla).
613- `ContextoCapacidades` gana `indice_app`: la identidad de cada app en el
614 manifiesto, con la que las capacidades de estado hallan SU ranura — jamás la
615 de otra.
616
617### Cambiado
618- `AplicacionWasm::cargar` y `encender_app` reciben el `indice_app` de la app.
619- `cargar_userspace` instala el manifiesto VIVO ANTES de instanciar las apps:
620 el `init` de una app ya consulta su estado persistido al despertar.
621- `almacen::fijar_manifiesto` deja de ser `dead_code` — lo invoca `fijar_estado`.
622- La génesis (`boot`) sustituye la segunda instancia de `hola` por `memoriosa`
623 (región 700,120 de 360×80).
624
625### Verificado
626- QEMU (captura headless + `sendkey` por el monitor):
627 - **Disco virgen** — memoriosa arranca con 0 celdas y el testigo verde.
628 - **Cinco pulsaciones** (`sendkey`) — memoriosa pinta 5 celdas violetas y
629 persiste el recuento; `sys_estado_guardar` re-ancla el manifiesto.
630 - **Reinicio** — `boot` respeta el disco; memoriosa despierta con las 5
631 celdas intactas y el testigo en ámbar: su `init` releyó el estado del
632 grafo. El estado por-app sobrevivió al apagón.
CHANGELOG.md
Registro de cambios — renaser
Registro técnico detallado, fase a fase, en orden cronológico. Formato
inspirado en Keep a Changelog. Para la crónica en lenguaje llano, ver
DIARIO.md; para el estado y los planes, ROADMAP.md.
Fase 1 — El primer microsegundo — 2026-05-21
Infraestructura
- Espacio de trabajo Cargo,
kernel/Cargo.toml, .cargo/config.toml,
rust-toolchain.toml.
- Target inicial: especificación JSON propia
x86_64-renaser.json — PIC,
soft-float, sin SSE, enlazador lld.
Añadido
kernel/src/main.rs: punto de entrada con el macro entry_point! de
bootloader_api 0.11.
- Adopción y verificación con seguridad de tipos del framebuffer GOP (ancho,
alto, formato de píxel, stride).
- Lienzo intermedio estático de 8 MiB en
.bss, alineado a página: la técnica
de doble búfer para evitar parpadeos.
#[panic_handler] que dibuja una franja roja directamente sobre el
framebuffer mediante escrituras volátiles.
- Dependencias:
bootloader_api 0.11, x86_64 0.15, embedded-graphics 0.8.
Notas
- Corrección clave:
bootloader (constructor de imagen, lado anfitrión) no es
bootloader_api (la API no_std que consume el kernel).
Fase 1.5 — Empaquetado y arranque — 2026-05-21
Cambiado
- Migración del target del kernel: de la JSON propia
x86_64-renaser.json al
target nativo precompilado x86_64-unknown-none. Elimina build-std y la
burocracia de compilador asociada.
Añadido
- Miembro
boot/: orquestador de anfitrión. Dependencia de artefacto
(RFC 3028) sobre el kernel; construcción de la imagen de disco UEFI con
bootloader::UefiBoot; lanzador de QEMU.
Toolchain
- Instalación de la toolchain nightly (la máquina solo tenía la estable).
Corregido
- Los nightly recientes exigen
-Zjson-target-spec para usar specs JSON
(resuelto al migrar al target nativo).
- Sintaxis del acelerador de QEMU:
-machine q35,accel=kvm:tcg.
Verificado
- Arranque en QEMU: superficie índigo limpia a 1280×800.
- Baliza de pánico (franja roja) confirmada inyectando un pánico de prueba.
Fase 2.0 — Cimientos del manejo de fallos — 2026-05-21
Añadido
kernel/src/gdt.rs: GDT propia, TSS y un stack de emergencia (IST)
reservado para el manejador de doble fallo.
kernel/src/interrupts.rs: IDT con manejadores de excepción de CPU. El
breakpoint (#BP) es recuperable; #UD, #DE, #GP, #PF y #DF son fatales y
encienden la baliza.
#![feature(abi_x86_interrupt)].
Corregido
- La GDT debe recargar los registros SS/DS/ES con un segmento de datos del
kernel: el cargador deja
SS = 0x10, valor que en la GDT nueva pasa a ser el
descriptor del TSS; el primer iretq de una rutina de excepción provocaba un
#GP. Diagnosticado con la traza -d int de QEMU.
Verificado
- Excepción
int3 atrapada y superada; línea-latido teal dibujada como prueba.
Fase 2.1 — Interrupciones de hardware — 2026-05-21
Añadido
kernel/src/pic.rs: remapeo del par 8259 (PIC) fuera del rango de las
excepciones (vectores 0x20+); programación del temporizador PIT a 100 Hz;
IRQ1 de teclado.
- Bucle de render en
kernel_main, despertado por el temporizador, en
sustitución del hlt estático.
Verificado
- La línea-latido se anima (temporizador en marcha); las pulsaciones de teclado
inyectadas cambian el cuadro-eco (teclado en marcha).
Fase 3 — Memoria dinámica y reactor asíncrono — 2026-05-21
Añadido
kernel/src/memory/: linked_list_allocator como #[global_allocator]
sobre una región estática en .bss.
#[alloc_error_handler]: franja naranja de agotamiento de memoria.
#![feature(alloc_error_handler)].
kernel/src/async_system/: reactor cooperativo — Executor, Task/TaskId,
Waker (vía alloc::task::Wake) y ScancodeStream.
kernel/src/texto.rs: rasterización de tipografía vectorial con fontdue;
TTF Adwaita Mono empotrada con include_bytes!.
- Dependencias:
linked_list_allocator 0.10, spin 0.9, crossbeam-queue
0.3, futures-util 0.3, fontdue 0.9.
Cambiado
- Heap ampliado de 16 MiB a 64 MiB:
fontdue, al analizar una tipografía real,
agotaba los 16 MiB iniciales. El #[alloc_error_handler] lo delató pintando
la franja naranja — el guardarraíl funcionó en silicio real.
Corregido
- El modo
no_std de fontdue está condicionado a su feature hashbrown; sin
ella recae en std. Se fija default-features = false, features = ["hashbrown"].
Verificado
- Rótulo de bienvenida rasterizado al arranque; el texto tecleado aparece en vivo.
Fase 4 — El escudo de aislamiento WASM — 2026-05-21
Añadido
kernel/src/wasm/: intérprete wasmi 1.0.9 en modo no_std. env.rs
define la matriz de capacidades — exactamente dos funciones de host:
sys_render_frame(ptr, len), con validación infranqueable de los límites de
la memoria lineal, y sys_get_scancode().
apps/hello_wasm/: primera aplicación del userspace — módulo
wasm32-unknown-unknown (cdylib), un cuadrado móvil dirigido por teclado. Se
empotra en el kernel con include_bytes!.
- Dependencia:
wasmi 1.0.
Cambiado
- El kernel se EXCLUYE del espacio de trabajo (
exclude = ["kernel", "apps"]):
al añadir wasmi, cargo build intentaba compilar el kernel para el
anfitrión y fallaba por un símbolo _start duplicado. El kernel pasa a
construirse únicamente como dependencia de artefacto de boot.
Corregido
wasmi no_std necesita default-features = false, features = ["hash-collections"].
wasmi 1.0.9 usa Linker::instantiate_and_start (no existe instantiate).
Verificado
- La aplicación WASM pinta su propia superficie y responde a las pulsaciones de
teclado inyectadas, sin más vía hacia el kernel que las dos capacidades.
Mantenimiento — Estructura, documentación e integración — 2026-05-21
Cambiado
- Refactorización:
main.rs dividido de 692 a ~155 líneas. Se extraen los
módulos sync.rs, grafico.rs, consola.rs y baliza.rs. Sin cambios de
comportamiento (verificado en QEMU).
Añadido
- Documentación:
CLAUDE.md, README.md, ARCHITECTURE.md, ROADMAP.md,
CHANGELOG.md y DIARIO.md.
- Integración con git: repositorio inicializado, remoto
origin en Gitea,
.gitignore.
Seguridad
renaser.txt —un borrador de trabajo que contenía una credencial— se subió
por error en el commit inicial. Se purgó de todo el historial con
git filter-branch y se reescribió el remoto con push --force. La
credencial expuesta debe rotarse.
Fase 5 — Multitarea cooperativa, guardarrail de fuel y reloj — 2026-05-22
Unificación de la Fase 3 (reactor) y la Fase 4 (WASM): el userspace deja de ser
una sola app que monopoliza la CPU y pasa a ser un conjunto de aplicaciones
cooperativas, aisladas también en el TIEMPO.
Añadido
kernel/src/async_system/reloj.rs: convierte la IRQ0 (PIT, 100 Hz) en una
primitiva asíncrona. CONTADOR_PULSOS (AtomicU64) y EsperaFrame, un
Future que se resuelve en el siguiente pulso — la unidad de cesión
cooperativa del userspace. Censo de wakers tras Mutex, drenado por la IRQ0.
kernel/src/wasm: ABI de fotograma. AplicacionWasm —instancia PERSISTENTE
entre fotogramas (Store + TypedFunc<(), ()> + región)— sustituye al
wasm::ejecutar de fuego-y-olvido. El módulo del userspace exporta ahora
init() (una vez) y tick() (un fotograma, y retorna).
- Escudo de combustible:
Engine con Config::consume_fuel(true) y
CompilationMode::Eager (el fuel mide solo ejecución). Presupuesto recargado
antes de cada tick — FUEL_ARRANQUE (20 M) y FUEL_FOTOGRAMA (2 M).
kernel/src/wasm/env.rs: capacidad sys_render_frame con regiones de
dibujo — cada fotograma se compone desplazado por el (offset_x, offset_y)
de la app; un tamaño ajeno a la región se rechaza. Canal de teclado por
aplicación: la IRQ1 difunde cada scancode a TODOS los canales, de modo que
varias apps reciben la entrada en paralelo sin robársela.
kernel/src/grafico.rs: RegionPantalla y Color::DESALOJO (púrpura).
apps/discola/: aplicación WASM construida para portarse mal — su tick es
un bucle cerrado. Demuestra el guardarrail de fuel en vivo.
Cambiado
apps/hello_wasm: migrada al ABI init/tick; su lienzo se dimensiona a la
región (480×560). El estado del cuadrado persiste en su memoria lineal.
interrupts.rs: la IRQ0 avanza el reloj; la IRQ1 difunde a los canales.
consola.rs: volcar_marco compone en una sub-región; pintar_desalojo
tatúa la baliza púrpura.
- Contención de fallos: el subsistema WASM ya no usa
.expect(). Toda falla
—carga, instanciación, desbordamiento, agotamiento de fuel— se devuelve como
Result; la tarea desaloja la app (baliza púrpura) y el kernel sigue vivo.
Verificado
- QEMU: tres apps concurrentes. Dos instancias de
hello_wasm (mismo bytecode,
regiones izquierda y derecha) renderizan y responden a W/A/S/D en paralelo,
con movimiento idéntico. La app díscola es desalojada en su primer fotograma
por agotamiento de combustible: su región queda púrpura y el sistema —kernel y
apps vecinas— no sufre un solo sobresalto.
Fase 6.0 — Cuotas de memoria y ciclo de vida del userspace — 2026-05-22
El aislamiento de las aplicaciones, que la Fase 5 hizo temporal (combustible),
se completa ahora en la dimensión espacial (memoria) y se cierra la fuga de
recursos del ciclo de vida.
Añadido
- Techo de memoria lineal por aplicación:
4 MiB. wasm/mod.rs construye un
StoreLimits (StoreLimitsBuilder::memory_size + trap_on_grow_failure) y lo
liga al Store con Store::limiter. Un memory.grow que rebase la cuota se
convierte en trampa; el kernel la captura y desaloja la app.
FallaApp::SinMemoria y Color::DESALOJO_MEMORIA (amarillo pálido). El
desalojo distingue la causa por color: púrpura (tiempo/aborto), amarillo
(memoria). La clasificación es robusta — Error::as_trap_code() da un código
público y unívoco: TrapCode::GrowthOperationLimited.
Drop para AplicacionWasm: al morir una app desalojada, su canal de teclado
se da de baja del censo de difusión de la IRQ1 — cierra la fuga señalada en la
Fase 5. async_system::teclado se reorganiza en crear_canal /
registrar_canal / cerrar_canal; el canal se inscribe al FINAL de la carga,
de modo que una carga fallida no deja canales huérfanos.
apps/glotona/: aplicación WASM construida para devorar memoria — su tick
invoca memory.grow sin freno. Demuestra el guardarrail espacial en vivo.
Verificado
- QEMU: cuatro apps concurrentes. Las dos
hello_wasm renderizan y responden al
teclado en paralelo; la app díscola es desalojada por combustible (franja
púrpura) y la app glotona por cuota de memoria (franja amarilla), ambas en su
primer fotograma, sin que el kernel ni las apps honradas se inmuten.
Fase 6.1a — Sonda PCI y disco de pruebas — 2026-05-22
Primer paso de la estrategia incremental hacia el almacenamiento: derribar el
muro del descubrimiento de hardware antes de diseñar nada encima.
Añadido
kernel/src/drivers/: nuevo subsistema de drivers. pci.rs enumera el bus
PCI por fuerza bruta mediante el mecanismo de configuración #1 —puertos de E/S
0xCF8 (dirección) y 0xCFC (datos)—; recorre buses y dispositivos leyendo
el Vendor/Device ID, y localiza el disco virtio-blk (vendor 0x1AF4, device
0x1001/0x1042), devolviendo su ubicación y sus seis BARs.
kernel_main: tras fundar la consola, sondea el bus PCI y deja constancia
visual del hallazgo.
Cambiado
boot/src/main.rs: forja un disco de pruebas target/disk.img (fichero
disperso de 32 MiB; se respeta si ya existe) y lo adjunta a QEMU como
virtio-blk-pci. Corrección de plataforma: la máquina q35 es x86_64 y
exige la transmisión PCI; virtio-blk-device (su gemelo MMIO) es de ARM.
Verificado
- QEMU: el kernel enumera el bus y reporta en pantalla
virtio-blk en bus 0 dev 3 :: BAR0 E/S 0x6000 — un dispositivo virtio-blk
transicional (device 0x1001), con su BAR0 en espacio de E/S. El muro del
descubrimiento PCI queda derribado; las cuatro apps del userspace siguen
operando sin alteración.
Fase 6.1b — HAL, DMA y lectura del sector 0 — 2026-05-22
Segundo paso del sustrato de almacenamiento: el diálogo real con el disco. El
kernel monta el dispositivo virtio-blk y lee su primer sector por DMA.
Añadido
- Dependencia
virtio-drivers 1.13 (no_std, default-features = false,
feature alloc).
kernel/src/drivers/disco.rs:
- Asignador de marcos «bump» — reparte páginas físicas de 4 KiB para el
DMA, tomadas de la mayor región de RAM libre que el cargador reporta. No
libera: suficiente para una sonda (la gestión fina llegará con el grafo).
KernelHal — implementa el trait Hal de virtio-drivers. dma_alloc
entrega marcos físicos a cero; mmio_phys_to_virt traduce los BARs (el
cargador mapea ≥ 4 GiB de memoria física, que cubre todo MMIO de PCI);
share/unshare usan un buffer rebote para que cualquier región del kernel
pueda viajar al dispositivo.
montar_y_leer_sector0 — enumera el bus, habilita E/S + memoria +
bus-master, monta el PciTransport y el VirtIOBlk, y lee el sector 0 por
sondeo del used ring (sin depender aún de interrupciones).
kernel/src/drivers/pci.rs: reescrito como CamPuertos, la implementación de
ConfigurationAccess de virtio-drivers sobre los puertos 0xCF8/0xCFC.
kernel_main captura physical_memory_offset y la región de RAM de
BootInfo, funda el subsistema de disco y reporta el resultado de la sonda.
Cambiado
boot/src/main.rs: al forjar el disco de pruebas graba una firma
(renaser-6.1b) en su sector 0 — el testigo del viaje de ida y vuelta.
Verificado
- QEMU: el kernel reporta
virtio-blk :: bus 0 dev 3 :: 65536 sectores :: s0=renaser-6.1b. La firma grabada por el anfitrión se lee de vuelta intacta:
descubrimiento PCI, transporte, DMA y transferencia funcionan de punta a
punta. Las cuatro apps del userspace siguen operando sin alteración.
Fase 6.1c — El grafo de objetos direccionado por contenido — 2026-05-22
Tercer y último paso del sustrato de almacenamiento. El disco deja de ser un
dispositivo que se sondea y pasa a ser una MEMORIA QUE PERDURA: un grafo
dirigido acíclico de objetos direccionados por contenido — no un sistema de
archivos plano POSIX.
Añadido
kernel/src/almacen.rs — el grafo de objetos:
- Objeto — una carga útil de bytes (
datos) y una lista de aristas
(hijos: hashes de otros objetos). Las aristas hacen del almacén un DAG.
- Direccionamiento por contenido — la identidad de un objeto es el hash
BLAKE3 de su forma serializada. De ello se siguen dos propiedades que un
sistema de archivos jamás regala: INTEGRIDAD (el contenido leído se
rehashea y se verifica contra el hash pedido) y DEDUPLICACIÓN (contenido
idéntico produce el mismo hash; se almacena una sola vez).
- Disco como log — el sector 0 es el superbloque (magia
RENASGRF,
versión, cursor del log y hash de la raíz); tras él se anexan los registros
de objetos, [longitud u32 LE][payload postcard][relleno a cero]. Un
índice en memoria (hash -> sector) se reconstruye al arrancar recorriendo
el log de cabo a rabo.
- API:
init —monta el disco, lee o forja el superbloque, reconstruye el
índice—, almacenar, recuperar, raiz y fijar_raiz.
- Cinco capacidades nuevas del host en
wasm/env.rs — sys_object_put,
sys_object_datos, sys_object_hijo, sys_object_raiz y
sys_object_fijar_raiz —, con la misma validación infranqueable de límites
de la memoria lineal que sys_render_frame. Distinguen dos clases de fallo:
un puntero inválido ABORTA la app —es su culpa, se traduce en trampa—; un
fallo del almacenamiento le devuelve un código de error negativo —no lo es—.
apps/cronista/ — la primera aplicación del userspace que escribe en el
almacenamiento PERSISTENTE. En cada arranque consulta la raíz del grafo,
graba un objeto nuevo —datos: el número de arranque; hijos: la raíz
anterior, el eslabón del DAG— y lo corona como raíz. Pinta una celda por
arranque registrado y un testigo de integridad que recorre la cadena entera.
- Dependencias:
serde 1 y postcard 1 (serialización binaria compacta, el
formato que viaja al disco) y blake3 1 (la función hash). Las tres no_std.
Cambiado
kernel/src/drivers/disco.rs reescrito para un sustrato PERMANENTE:
- El asignador de marcos «bump» de la Fase 6.1b cede el paso a uno de MAPA DE
BITS con liberación real.
dma_dealloc y unshare devuelven los marcos a
la arena: un almacén vivo, con su trasiego incesante de DMA, ya no la agota.
- El
VirtIOBlk deja de montarse y destruirse en cada llamada: se monta UNA
vez y queda tras un Mutex global. leer_sectores / escribir_sectores
exponen la E/S de bloques — el disco deja de ser de solo lectura. Se retira
montar_y_leer_sector0, la sonda de un solo uso de la Fase 6.1b.
boot/src/main.rs: el disco de pruebas pasa a ser el disco de objetos. Ya
no se le graba una firma — se forja virgen, a cero, y el kernel lo formatea
la primera vez que no halle el superbloque.
main.rs: informar_disco (la sonda de la Fase 6.1b) se sustituye por
informar_almacen, que funda el grafo. El userspace pasa de cuatro a cinco
apps; las regiones de discola y glotona se reajustan para alojar a cronista.
Notas
- blake3 forzado a escalar. El target del kernel corre sin SSE; un camino
SIMD de blake3 activado por detección en tiempo de ejecución ejecutaría
instrucciones que la CPU, sin
CR4.OSFXSR, rechazaría con un #UD. Se fija
blake3 con pure + los cuatro no_* (sse2, sse41, avx2, avx512):
implementación puramente escalar, sin SIMD ni ensamblador.
Verificado
- QEMU, TRES arranques consecutivos sobre el mismo disco persistente: la app
cronista pinta 1, luego 2, luego 3 celdas — la cuenta de arranques sobrevive
a los reinicios porque vive en el grafo, en el disco, no en la RAM. El
testigo de integridad queda VERDE en los tres: el DAG entero se recorre, de
la raíz al primer eslabón, y su profundidad cuadra con la cuenta. El
superbloque en disco confirma la magia
RENASGRF, versión 1, cursor 4 y la
raíz del tercer arranque; el registro del primer arranque guarda datos = 1
y cero hijos. Las otras cuatro apps siguen su curso sin alteración — las dos
hello_wasm renderizando, discola desalojada en púrpura, glotona en amarillo.
Fase 6.2 — E/S de disco asíncrona por interrupción — 2026-05-22
La Fase 6.1 hizo hablar al disco, pero por SONDEO: el procesador se quedaba en
espera activa vigilando el used ring de virtio, incapaz de atender nada más.
La 6.2 libera el planificador cooperativo — la E/S de bloques pasa a ser
REACTIVA, guiada por la interrupción física del dispositivo.
Añadido
EsperaDisco (drivers/disco.rs): una transferencia de bloques expresada
como Future nativo. Posee sus buferes DMA —BlkReq, BlkResp y los datos,
en el heap para una dirección estable—; su poll envía la petición por la
API NO BLOQUEANTE de virtio-drivers (read_blocks_nb/write_blocks_nb),
consulta el used ring (peek_used) y, si la transferencia sigue en vuelo,
inscribe el waker y cede. leer_bloques/escribir_bloques lo construyen.
- La IRQ del disco:
montar descubre la línea de IRQ legada del dispositivo
(registro «Interrupt Line», offset 0x3C del espacio de configuración PCI),
registra un manejador en la IDT y abre la línea en el PIC.
interrupts::irq_disco → disco::atender_irq reconoce la interrupción en el
dispositivo —leer su registro ISR baja la línea INTx— y despierta, vía un
waker de ranura única, a la tarea que aguardaba el bloque.
bloquear_en — el puente para los contextos SÍNCRONOS (el arranque, las
capacidades WASM, que no pueden .await): lleva un Future de disco hasta
su final durmiendo la CPU con hlt —la despiertan la IRQ del disco o el
temporizador, como red de seguridad—; jamás en espera activa con el sistema
ya en marcha.
pic::desenmascarar / pic::vector_irq: abrir una línea concreta del par
8259 (con su cascada, si vive en el esclavo) y mapear línea → vector de IDT.
pci::linea_irq: leer el registro «Interrupt Line» de un dispositivo.
tarea_sonda_disco (main.rs): una tarea del reactor que lee el sector 0 de
forma asíncrona — la prueba viva de que la IRQ conduce la E/S sin detener a
las aplicaciones.
Cambiado
leer_sectores/escribir_sectores se reescriben sobre la maquinaria
asíncrona (bloquear_en + EsperaDisco). El almacen y las capacidades
sys_object_* NO cambian una línea: heredan la E/S por interrupción de
forma transparente — la espera de disco deja de quemar ciclos en sondeo.
- El
VirtIOBlk pide al dispositivo que emita interrupciones al completar cada
petición (enable_interrupts).
Decisiones de ingeniería
- PIC, no IOAPIC. El kernel corre íntegramente sobre el par 8259 (
pic.rs)
y la crate x86_64 no ofrece abstracción de APIC. Las IRQ legadas de PCI de
la máquina q35 se enrutan por el 8259: basta leer la línea que el firmware
UEFI ya asignó y abrirla. Migrar al IOAPIC habría exigido levantar LAPIC +
IOAPIC + parseo de tablas ACPI — un subsistema entero, desproporcionado para
el objetivo. El resultado funcional es idéntico: E/S conducida por la
interrupción física.
- Las capacidades WASM no
.await-ean. El intérprete wasmi ejecuta las
funciones de host de forma SÍNCRONA: no hay manera de suspender un módulo a
mitad de una llamada de host. Por eso sys_object_* no se vuelven
asíncronas; usan bloquear_en, que duerme la CPU con hlt en lugar de
sondear. El verdadero solapamiento E/S ↔ render lo aprovechan las TAREAS del
reactor (tarea_sonda_disco hoy; la carga dinámica de módulos, mañana).
- Una IRQ legada de PCI es de NIVEL: el manejador reconoce primero al
dispositivo —lo que baja su línea— y sólo después cierra el EOI del PIC; el
orden inverso reavivaría la interrupción en bucle.
- Todo acceso al disco toma su
Mutex con las interrupciones desactivadas
(without_interrupts): la IRQ del disco jamás encuentra el cerrojo ocupado,
lo que hace imposible el interbloqueo por interrupción.
Verificado
- QEMU: el disco virtio-blk se enruta a la IRQ 11. La
tarea_sonda_disco
reporta «sonda asíncrona OK -- 2 IRQ de disco atendidas»: la interrupción del
disco dispara de verdad. La app cronista, sobre el disco persistente heredado
de la Fase 6.1c, continúa la cuenta de arranques (3 → 4 → 5 celdas) con el
testigo de integridad del DAG en verde — la nueva E/S asíncrona lee y escribe
el grafo sin un solo fallo. Las cuatro apps WASM siguen su curso, sin un
sobresalto ni un micro-congelamiento.
Fase 7 — Apertura: Apps que nacen del Grafo — 2026-05-22
Apertura de la Fase 7. Este commit asienta el plan y el andamiaje; no
cambia el comportamiento del kernel. La Fase 7 destierra el
include_bytes! del userspace: las aplicaciones pasan a ser objetos del
grafo, y un Manifiesto de Génesis —también en el grafo— dicta qué arranca,
con qué cuota y en qué región.
Añadido
FASE7.md — el plan de ataque: el objetivo, el problema huevo-y-gallina
de la génesis, las sub-fases 7a (manifiesto + carga desde el grafo, con
semilla por el kernel), 7b (siembra de la imagen por boot, muerte del
include_bytes!) y 7c (persistencia inter-sesión), y los guardarraíles.
kernel/src/manifiesto.rs — andamiaje del Manifiesto de Génesis. Los
tipos Manifiesto / EntradaApp y la (de)serialización postcard están
completos; cargar y sembrar_genesis son esbozos hasta la Fase 7a.
La sub-región se guarda en campos u32 de ancho fijo (formato en disco),
no como RegionPantalla (usize, ancho de plataforma).
Notas
- El módulo se declara en
main.rs pero aún no se cablea a kernel_main
(#![allow(dead_code)] temporal, hasta que la 7a le dé un llamador). El
kernel compila y se comporta idéntico a la Fase 6.2 — nada observable que
verificar en QEMU en esta apertura.
Fase 7a — Apps que nacen del Grafo: el Manifiesto — 2026-05-22
El kernel deja de empotrar el userspace. Las cinco aplicaciones ya no llegan
por include_bytes! en main.rs: nacen del grafo de objetos, gobernadas por
un Manifiesto de Génesis que también vive en el grafo.
Añadido
kernel/src/manifiesto.rs — el Manifiesto de Génesis implementado: tipos
Manifiesto / EntradaApp, (de)serialización postcard, cargar (lee el
manifiesto del grafo vía el ancla del superbloque) y sembrar_genesis
(puebla un disco virgen con las cinco apps de génesis).
almacen: el SuperBloque gana el ancla manifiesto: Option<Hash> —
gemela de raiz, pero del lado del kernel. Accesores manifiesto() y
fijar_manifiesto().
Cambiado
almacen::VERSION 1 → 2: el superbloque cambia de forma. Un disco v1 se
reformatea al arrancar, como cualquier disco ajeno — la cuenta de la
cronista reinicia una vez.
kernel_main: las cinco llamadas encender_app escritas a mano se
sustituyen por cargar_userspace, que carga el manifiesto —o lo siembra si
el disco está virgen— e itera sus EntradaApp.
encender_app recibe una EntradaApp y recupera el bytecode del grafo.
recuperar ya recomputa y verifica el hash: un bytecode corrupto se niega,
se pinta la baliza de desalojo en su región y el arranque continúa.
wasm::AplicacionWasm::cargar recibe el techo de memoria por-app (del
manifiesto) en vez de la constante global TECHO_MEMORIA.
Notas
- La siembra es TRANSITORIA: el bytecode aún viaja empotrado en
manifiesto.rs (include_bytes!) como semilla. La Fase 7b lo moverá al
constructor de imagen boot y el include_bytes! morirá del todo.
Verificado
- QEMU (captura headless por el monitor,
screendump): los dos caminos del
Manifiesto confirmados.
- Disco virgen — el almacén reporta «disco formateado :: 65536 sectores
:: 0 objetos», la consola imprime «manifiesto :: genesis sembrada en disco
virgen» y «manifiesto :: 5 apps nacidas del grafo». La pantalla es
idéntica a la Fase 6.2: dos
hello_wasm renderizando, discola desalojada
en púrpura, glotona en amarillo, cronista pintando su primera celda.
- Disco ya sembrado — sin línea de siembra: el almacén reporta «grafo
montado :: 6 objetos :: raíz presente» (las 5 semillas más el objeto que
la cronista persistió), la consola imprime sólo «manifiesto :: 5 apps
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.
Fase 7c — Persistencia inter-sesión: cada app recuerda lo suyo — 2026-05-22
La cronista dejaba huella, pero en la RAÍZ del grafo — un ancla única que sólo
una app puede usar. La 7c estrena la persistencia POR-APP: cada EntradaApp
del Manifiesto de Génesis tiene su propia ranura estado, y una app guarda y
recobra lo suyo sin pisar a nadie. El estado de cada aplicación sobrevive, por
separado, a un reinicio.
Añadido
apps/memoriosa — nueva app WASM interactiva. Cuenta las teclas pulsadas
en toda su historia y persiste el recuento; al reiniciar, despierta con su
cuenta intacta. Una celda violeta por pulsación; el testigo de la esquina es
verde si nació limpia, ámbar si despertó con memoria de una vida anterior.
kernel — dos capacidades del host nuevas:
sys_estado_cargar(salida, capacidad) — copia el estado persistido de la
app que llama en su memoria lineal; 0 si aún no tiene estado previo.
sys_estado_guardar(datos, datos_len) — graba datos como objeto del
grafo y ancla su hash en la EntradaApp de la app, re-grabando y
re-anclando el manifiesto.
manifiesto.rs — el manifiesto VIVO: VIVO, un Mutex<Manifiesto> que el
kernel custodia. instalar (en el arranque), estado_de (lee la ranura de
una app) y fijar_estado (la muta, re-graba el manifiesto y lo re-ancla).
ContextoCapacidades gana indice_app: la identidad de cada app en el
manifiesto, con la que las capacidades de estado hallan SU ranura — jamás la
de otra.
Cambiado
AplicacionWasm::cargar y encender_app reciben el indice_app de la app.
cargar_userspace instala el manifiesto VIVO ANTES de instanciar las apps:
el init de una app ya consulta su estado persistido al despertar.
almacen::fijar_manifiesto deja de ser dead_code — lo invoca fijar_estado.
- La génesis (
boot) sustituye la segunda instancia de hola por memoriosa
(región 700,120 de 360×80).
Verificado
- QEMU (captura headless +
sendkey por el monitor):
- Disco virgen — memoriosa arranca con 0 celdas y el testigo verde.
- Cinco pulsaciones (
sendkey) — memoriosa pinta 5 celdas violetas y
persiste el recuento; sys_estado_guardar re-ancla el manifiesto.
- Reinicio —
boot respeta el disco; memoriosa despierta con las 5
celdas intactas y el testigo en ámbar: su init releyó el estado del
grafo. El estado por-app sobrevivió al apagón.