El userspace gana un sentido del tiempo: hasta ahora una app solo sabía cuántas veces la habían llamado, no cuánto tiempo había pasado. - Capacidad `sys_tiempo_mono() -> u64` — la décima función del host: los milisegundos monótonos desde el arranque. `reloj` expone la cuenta del PIT (100 Hz) como `milisegundos()`; `env` la inyecta. Lectura pura, no toca la memoria del módulo, jamás retrocede. - App nueva `pulso` (`apps/pulso/`, wasm32): un compás visual cuya escena es una función PURA de `sys_tiempo_mono` — sin estado entre fotogramas—. Dos instancias laten al unísono nazcan cuando nazcan. - `GENESIS` crece de 5 a 6 apps; `pulso` es la maestra del escritorio. Verificado en QEMU (sendkey): la barra de `pulso` avanza con el tiempo de pared; un segundo `pulso` lanzado con Alt+N ~15 s después aparece sincronizado con el primero — el compás se rige por el reloj absoluto, no por una cuenta de fotogramas. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
46 KiB
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, enlazadorlld.
Añadido
kernel/src/main.rs: punto de entrada con el macroentry_point!debootloader_api0.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_api0.11,x86_640.15,embedded-graphics0.8.
Notas
- Corrección clave:
bootloader(constructor de imagen, lado anfitrión) no esbootloader_api(la APIno_stdque 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.jsonal target nativo precompiladox86_64-unknown-none. Eliminabuild-stdy 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 conbootloader::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-specpara 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 primeriretqde una rutina de excepción provocaba un #GP. Diagnosticado con la traza-d intde QEMU.
Verificado
- Excepción
int3atrapada 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 delhltestá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_allocatorcomo#[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íaalloc::task::Wake) yScancodeStream.kernel/src/texto.rs: rasterización de tipografía vectorial confontdue; TTF Adwaita Mono empotrada coninclude_bytes!.- Dependencias:
linked_list_allocator0.10,spin0.9,crossbeam-queue0.3,futures-util0.3,fontdue0.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_stddefontdueestá condicionado a su featurehashbrown; sin ella recae enstd. Se fijadefault-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érpretewasmi1.0.9 en modono_std.env.rsdefine 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, ysys_get_scancode().apps/hello_wasm/: primera aplicación del userspace — módulowasm32-unknown-unknown(cdylib), un cuadrado móvil dirigido por teclado. Se empotra en el kernel coninclude_bytes!.- Dependencia:
wasmi1.0.
Cambiado
- El kernel se EXCLUYE del espacio de trabajo (
exclude = ["kernel", "apps"]): al añadirwasmi,cargo buildintentaba compilar el kernel para el anfitrión y fallaba por un símbolo_startduplicado. El kernel pasa a construirse únicamente como dependencia de artefacto deboot.
Corregido
wasmino_stdnecesitadefault-features = false, features = ["hash-collections"].wasmi1.0.9 usaLinker::instantiate_and_start(no existeinstantiate).
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.rsdividido de 692 a ~155 líneas. Se extraen los módulossync.rs,grafico.rs,consola.rsybaliza.rs. Sin cambios de comportamiento (verificado en QEMU).
Añadido
- Documentación:
CLAUDE.md,README.md,ARCHITECTURE.md,ROADMAP.md,CHANGELOG.mdyDIARIO.md. - Integración con git: repositorio inicializado, remoto
originen 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 congit filter-branchy se reescribió el remoto conpush --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) yEsperaFrame, unFutureque se resuelve en el siguiente pulso — la unidad de cesión cooperativa del userspace. Censo de wakers trasMutex, drenado por la IRQ0.kernel/src/wasm: ABI de fotograma.AplicacionWasm—instancia PERSISTENTE entre fotogramas (Store+TypedFunc<(), ()>+ región)— sustituye alwasm::ejecutarde fuego-y-olvido. El módulo del userspace exporta ahorainit()(una vez) ytick()(un fotograma, y retorna).- Escudo de combustible:
EngineconConfig::consume_fuel(true)yCompilationMode::Eager(elfuelmide solo ejecución). Presupuesto recargado antes de cadatick—FUEL_ARRANQUE(20 M) yFUEL_FOTOGRAMA(2 M). kernel/src/wasm/env.rs: capacidadsys_render_framecon 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:RegionPantallayColor::DESALOJO(púrpura).apps/discola/: aplicación WASM construida para portarse mal — sutickes un bucle cerrado. Demuestra el guardarrail de fuel en vivo.
Cambiado
apps/hello_wasm: migrada al ABIinit/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 elreloj; la IRQ1 difunde a los canales.consola.rs:volcar_marcocompone en una sub-región;pintar_desalojotatú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 comoResult; 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.rsconstruye unStoreLimits(StoreLimitsBuilder::memory_size+trap_on_grow_failure) y lo liga alStoreconStore::limiter. Unmemory.growque rebase la cuota se convierte en trampa; el kernel la captura y desaloja la app. FallaApp::SinMemoriayColor::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.DropparaAplicacionWasm: 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::tecladose reorganiza encrear_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 — sutickinvocamemory.growsin freno. Demuestra el guardarrail espacial en vivo.
Verificado
- QEMU: cuatro apps concurrentes. Las dos
hello_wasmrenderizan 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.rsenumera el bus PCI por fuerza bruta mediante el mecanismo de configuración #1 —puertos de E/S0xCF8(dirección) y0xCFC(datos)—; recorre buses y dispositivos leyendo el Vendor/Device ID, y localiza el disco virtio-blk (vendor0x1AF4, device0x1001/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 pruebastarget/disk.img(fichero disperso de 32 MiB; se respeta si ya existe) y lo adjunta a QEMU comovirtio-blk-pci. Corrección de plataforma: la máquinaq35es 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 (device0x1001), 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-drivers1.13 (no_std,default-features = false, featurealloc). 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 eltrait Haldevirtio-drivers.dma_allocentrega marcos físicos a cero;mmio_phys_to_virttraduce los BARs (el cargador mapea ≥ 4 GiB de memoria física, que cubre todo MMIO de PCI);share/unshareusan 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 elPciTransporty elVirtIOBlk, y lee el sector 0 por sondeo del used ring (sin depender aún de interrupciones).
kernel/src/drivers/pci.rs: reescrito comoCamPuertos, la implementación deConfigurationAccessdevirtio-driverssobre los puertos0xCF8/0xCFC.kernel_maincapturaphysical_memory_offsety la región de RAM deBootInfo, 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,raizyfijar_raiz.
- Objeto — una carga útil de bytes (
- Cinco capacidades nuevas del host en
wasm/env.rs—sys_object_put,sys_object_datos,sys_object_hijo,sys_object_raizysys_object_fijar_raiz—, con la misma validación infranqueable de límites de la memoria lineal quesys_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:
serde1 ypostcard1 (serialización binaria compacta, el formato que viaja al disco) yblake31 (la función hash). Las tresno_std.
Cambiado
kernel/src/drivers/disco.rsreescrito 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_deallocyunsharedevuelven los marcos a la arena: un almacén vivo, con su trasiego incesante de DMA, ya no la agota. - El
VirtIOBlkdeja de montarse y destruirse en cada llamada: se monta UNA vez y queda tras unMutexglobal.leer_sectores/escribir_sectoresexponen la E/S de bloques — el disco deja de ser de solo lectura. Se retiramontar_y_leer_sector0, la sonda de un solo uso de la Fase 6.1b.
- El asignador de marcos «bump» de la Fase 6.1b cede el paso a uno de MAPA DE
BITS con liberación real.
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 porinformar_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 conpure+ los cuatrono_*(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 guardadatos = 1y cero hijos. Las otras cuatro apps siguen su curso sin alteración — las doshello_wasmrenderizando, 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 comoFuturenativo. Posee sus buferes DMA —BlkReq,BlkRespy los datos, en el heap para una dirección estable—; supollenvía la petición por la API NO BLOQUEANTE devirtio-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_bloqueslo construyen.- La IRQ del disco:
montardescubre 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_irqreconoce 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 unFuturede disco hasta su final durmiendo la CPU conhlt—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_sectoresse reescriben sobre la maquinaria asíncrona (bloquear_en+EsperaDisco). Elalmaceny las capacidadessys_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
VirtIOBlkpide 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 cratex86_64no ofrece abstracción de APIC. Las IRQ legadas de PCI de la máquinaq35se 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érpretewasmiejecuta las funciones de host de forma SÍNCRONA: no hay manera de suspender un módulo a mitad de una llamada de host. Por esosys_object_*no se vuelven asíncronas; usanbloquear_en, que duerme la CPU conhlten lugar de sondear. El verdadero solapamiento E/S ↔ render lo aprovechan las TAREAS del reactor (tarea_sonda_discohoy; 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
Mutexcon 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_discoreporta «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 porboot, muerte delinclude_bytes!) y 7c (persistencia inter-sesión), y los guardarraíles.kernel/src/manifiesto.rs— andamiaje del Manifiesto de Génesis. Los tiposManifiesto/EntradaAppy la (de)serializaciónpostcardestán completos;cargarysembrar_genesisson esbozos hasta la Fase 7a. La sub-región se guarda en camposu32de ancho fijo (formato en disco), no comoRegionPantalla(usize, ancho de plataforma).
Notas
- El módulo se declara en
main.rspero aún no se cablea akernel_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: tiposManifiesto/EntradaApp, (de)serializaciónpostcard,cargar(lee el manifiesto del grafo vía el ancla del superbloque) ysembrar_genesis(puebla un disco virgen con las cinco apps de génesis).almacen: elSuperBloquegana el anclamanifiesto: Option<Hash>— gemela deraiz, pero del lado del kernel. Accesoresmanifiesto()yfijar_manifiesto().
Cambiado
almacen::VERSION1 → 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 llamadasencender_appescritas a mano se sustituyen porcargar_userspace, que carga el manifiesto —o lo siembra si el disco está virgen— e itera susEntradaApp.encender_apprecibe unaEntradaAppy recupera el bytecode del grafo.recuperarya 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::cargarrecibe el techo de memoria por-app (del manifiesto) en vez de la constante globalTECHO_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 imagenbooty elinclude_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_wasmrenderizando, 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.
- 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
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ónpostcard, 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ónboot: 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.wasmse lee dekernel/assets/en tiempo de ejecución.
Cambiado
kernel/manifiesto.rs: pierde los cuatroinclude_bytes!, el descriptorAppGenesis,genesis()ysembrar_genesis(). Se reduce acargar()—leer el manifiesto del grafo— yregion()—traducir la región en disco a la del kernel—. Los tiposManifiesto/EntradaAppmigran aformato.kernel/almacen.rs: los tiposObjeto/SuperBloque, las constantes del formato, el hash y el trazado de registros migran aformato. 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 —bootlo hace siempre al forjar la imagen—; el kernel se levanta sin userspace y lo reporta.kernel/Cargo.toml: deja de declararserde/postcard/blake3por su cuenta; los hereda —mismas features, BLAKE3 escalar puro— a través deformato.wasm/mod.rs: se retira la constanteTECHO_MEMORIA, ya sin uso (el techo por-app lo dicta el manifiesto; el valor de génesis vive ahora enboot)..cargo/config.toml: el aliascargo kernelpasa de-p kernela--manifest-path kernel/Cargo.toml. Conformatocompartido entre el kernel yboot, pedir-p kerneldentro 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 —
bootimprime «disco de objetos sembrado :: 5 objetos, manifiesto anclado»; el kernel monta el grafo deboot—«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 —
bootrespeta 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 porboot.
- Disco virgen —
cargo build -p bootycargo kernelcompilan limpios;cargo testdeformato— 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)— grabadatoscomo objeto del grafo y ancla su hash en laEntradaAppde la app, re-grabando y re-anclando el manifiesto.
manifiesto.rs— el manifiesto VIVO:VIVO, unMutex<Manifiesto>que el kernel custodia.instalar(en el arranque),estado_de(lee la ranura de una app) yfijar_estado(la muta, re-graba el manifiesto y lo re-ancla).ContextoCapacidadesganaindice_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::cargaryencender_appreciben elindice_appde la app.cargar_userspaceinstala el manifiesto VIVO ANTES de instanciar las apps: elinitde una app ya consulta su estado persistido al despertar.almacen::fijar_manifiestodeja de serdead_code— lo invocafijar_estado.- La génesis (
boot) sustituye la segunda instancia deholapormemoriosa(región 700,120 de 360×80).
Verificado
- QEMU (captura headless +
sendkeypor 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_guardarre-ancla el manifiesto. - Reinicio —
bootrespeta el disco; memoriosa despierta con las 5 celdas intactas y el testigo en ámbar: suinitreleyó el estado del grafo. El estado por-app sobrevivió al apagón.
Fase 8a — El compositor teselante — 2026-05-22
Hasta la Fase 7, cada app llevaba su región escrita a mano en el manifiesto —
coordenadas fijas, una composición rígida. La Fase 8 entrega esa decisión a un
COMPOSITOR: el kernel ya no coloca las ventanas a mano, las TESELA con
mirada-layout.
Añadido
kernel/compositor.rs— el compositor teselante. Enlazamirada-layout—el mismo núcleono_stdque ordena las ventanas del compositor Wayland de brahman, ya enlazado porpathcruzando la frontera de workspace— y, con su algoritmoMasterStack, calcula un marco para cada app.disponer(n, …)devuelve un marco por ventana;area_apps, la zona teselable (la pantalla menos la franja superior de la consola).consola:pintar_escenario—inunda el área de apps y tiñe cada marco con el color de panel, de modo que el teselado se vea como una rejilla— y el colorColor::PANEL.
Cambiado
consola::volcar_marcoya no deposita el fotograma en(region.x, region.y): CENTRA el fotograma natural de la app dentro de su marco teselado y lo recorta a sus bordes.ContextoCapacidadeslleva ahoramarco(el rectángulo teselado) ynatural_ancho/natural_alto(el tamaño del lienzo de la app);sys_render_framevalida el fotograma contra el tamaño natural.AplicacionWasm/encender_app/cargar_userspacecablean el marco teselado.cargar_userspacerecibe las dimensiones de pantalla, tesela con el compositor y pinta el escenario antes de encender las apps.RegionPantalla::pixelesse retira — sin uso desde quesys_render_framemide contra el tamaño natural.- Las apps NO cambian. Renderizan su lienzo natural, fijo; el kernel lo compone centrado en el marco. El compositor reordena la pantalla sin que ninguna app toque una sola instrucción.
Verificado
- QEMU (captura headless): las cinco apps de génesis aparecen teseladas en
MasterStack—holacomo ventana maestra (izquierda, 60% del ancho) y memoriosa, discola, glotona y cronista apiladas a la derecha. Cada lienzo, centrado en su panel; las apps desalojadas (discola, glotona) tiñen su marco entero con la baliza. El registro de arranque permanece legible en la franja superior de la consola.
Fases 8b y 8c — El escritorio interactivo — 2026-05-22
El compositor de la Fase 8a teselaba, pero era inmóvil. Las Fases 8b y 8c lo hacen VIVO: el teclado reordena el escritorio en caliente y mueve el foco. Y para que las apps estáticas no se queden atrás en una reordenación, el kernel asume la persistencia visual con una caché de fotogramas.
Añadido
- Caché de fotogramas (backbuffer). Cada ventana del compositor guarda, en
RAM del kernel, el último fotograma que su app envió. La caché se reserva UNA
vez, acotada al lienzo natural de la app —jamás crece—. Al re-teselar o mover
el foco, el kernel recompone cada ventana desde su caché: una app que sólo
pintó en su
init—la cronista— conserva su imagen intacta a través de cualquier reordenación, sin enterarse del cambio geométrico. compositor: el escritorio interactivo. Un registroESCRITORIO—las ventanas, sus marcos, sus cachés, el modo de teselado—;presentar_fotograma(cachea y compone),desalojar,atender_mandos,ciclar_layout,mover_foco. El foco vive en unAtomicUsize; los mandos del teclado, en una cola lock-free.tarea_compositor— atiende los mandos del teclado en cada fotograma del reactor: el contexto cooperativo donde es seguro re-teselar y recomponer.Color::FOCO(índigo brillante) yColor::SIN_FOCO(gris mate) — el borde de cada ventana, que delata de un vistazo quién tiene el foco.
Cambiado
- Teclado (Fase 8c). El manejador de IRQ1 deja de difundir a ciegas:
- La tecla Alt es el modificador del sistema.
Alt+Espaciocicla el modo de teselado;Alt+J/Alt+Kmueven el foco. Estos mandos se consumen en la IRQ — jamás llegan a una app. - Una tecla ordinaria se entrega SÓLO a la app ENFOCADA. El censo de canales
se reindexó por
indice_app(Vec<Option<CanalTeclado>>): el foco —un índice— elige el canal exacto.
- La tecla Alt es el modificador del sistema.
consola::volcar_marcoypintar_desalojotrazan el borde de foco de la ventana.sys_render_frameya no compone directo: delega encompositor::presentar_fotograma, que cachea antes de componer.AplicacionWasm/ContextoCapacidadespierden elmarco—ahora vive en el registro del compositor, mutable—; conservan sólo elindice_app.
Exclusión de interrupciones (guardarraíl)
- El registro
ESCRITORIOlo tocan SÓLO tareas cooperativas; el manejador de IRQ1 jamás lo bloquea. La IRQ se comunica con el mundo cooperativo por un canal estrecho y a prueba de interbloqueos: dos atómicos —el foco y el estado de Alt— y una cola lock-free de mandos. Ningún cerrojo en disputa entre la interrupción y una tarea. - Las cachés se reservan en
compositor::fundar, una sola vez, al tamaño natural de cada app: ninguna asignación dinámica dentro del bucle del reactor.
Verificado
- QEMU (captura headless +
sendkeypor el monitor):- Arranque — cinco apps teseladas en
MasterStack;holaenfocada luce el borde índigo, las demás el gris. Alt+Espacio— el escritorio cicla aCenteredMaster; discola, glotona y cronista conservan su contenido a través del re-teselado: la caché de fotogramas en acción.Alt+J— el foco salta deholaamemoriosa; el borde índigo se mueve con él.- Enrutamiento — con
memoriosaenfocada, cuatro pulsaciones llegan sólo a ella —cuatro celdas violetas—; las demás apps, intactas.
- Arranque — cinco apps teseladas en
Fase 8d — Manipulación de ventanas — 2026-05-22
El escritorio de la 8c se podía recorrer con la vista —el foco— pero no reordenar. La 8d lo hace MANIPULABLE: el orden de teselado se separa de la identidad de las ventanas, y el teclado promueve y reordena.
Añadido
Escritorioganaorden: Vec<usize>— una permutación que dice qué ventana ocupa cada celda del teselado. Separar el orden de la identidad (indice_app) permite mover una ventana sin tocar su canal de teclado ni su ranura de estado: cambia de pared, no de identidad.aplicar_teselado— recalcula los marcos y los reparte según el orden.- Mandos
Promover,MoverAdelante,MoverAtras:Alt+Enter— promueve la ventana enfocada a la celda maestra; las demás se desplazan una posición.Alt+L/Alt+H— mueven la ventana enfocada adelante / atrás en el orden, intercambiándola con su vecina.
Cambiado
mover_focorecorre ahora el ORDEN de teselado —no los índices crudos—: el foco salta entre ventanas visualmente contiguas.ciclar_layoutyfundardelegan enaplicar_teselado.
Verificado
- QEMU (
sendkey): conmemoriosaenfocada,Alt+Enterla promueve a la ventana maestra yholabaja a la pila;Alt+Lla devuelve a la pila yholarecupera la maestra. El foco —el borde índigo— viaja siempre con la ventana, no con la celda.
Fase 9 — Orden-Z y ventanas flotantes — 2026-05-22
Hasta la 8d el escritorio era TESELADO puro: las ventanas repartían la pantalla sin solaparse jamás. La Fase 9 introduce un segundo modelo de composición —el SOLAPAMIENTO—: una ventana puede abandonar el teselado y FLOTAR, con un marco propio y libre, por encima de las demás.
Añadido
Escritorioganaflotantes: Vec<usize>— la pila de ventanas flotantes en orden-Z, de atrás hacia adelante;flotantes.last()es la frontal. Junto conordenforma una partición de las ventanas: cada una está teselada o flota, nunca en ambas ni en ninguna.- Mando
Flotar(Alt+F): alterna la ventana enfocada entre teselada y flotante. Al flotar, abandona el teselado —que se recalcula para las que quedan—, recibe un marco propio (su lienzo natural más un reborde de cromo) colocado en CASCADA, y sube al frente del orden-Z. Al volver, se reincorpora al final del orden de teselado. compositor::recomponer— recompone el escritorio entero de una pasada: arma la lista de capas (las teseladas al fondo; las flotantes encima, de atrás hacia adelante) y se la entrega a la consola.consola::recomponery los tiposCapa/Contenido: la consola funde las capas EN ORDEN sobre el lienzo; el solapamiento se resuelve por el orden del pintado, sin recortes ni máscaras. Una sola presentación cierra la pasada.consola::componer_fotograma— el volcado de un fotograma natural centrado en su marco, extraído devolcar_marcopara compartirlo entre el camino rápido y la recomposición.
Cambiado
presentar_fotogramaydesalojarbifurcan: sin ventanas flotantes conservan el camino RÁPIDO de la Fase 8 —pintar sólo la ventana que cambia—; con flotantes vivas recomponen el escritorio entero respetando el orden-Z.mover_focorecorre ahora TODAS las ventanas —las teseladas y las flotantes—; al enfocar una flotante, la alza al frente del orden-Z: la flotante con el foco está siempre delante.componer_escenario,ciclar_layout,promoverymover_ventanadelegan el repintado enrecomponer; desaparecenredibujar_todo,redibujar_ventanaypintar_escenario.
Verificado
- QEMU (
sendkey):Alt+Fsaca la ventana maestra del teselado y la deja flotando sobre las demás, que se re-teselan. Un segundoAlt+Fsobre otra ventana la flota en cascada, solapando a la primera.Alt+Kdevuelve el foco a la ventana grande y ésta sube al frente, tapando por completo a la pequeña. UnAlt+Ffinal la reintegra al teselado.
Fase 10 — Alta y baja de aplicaciones en vivo — 2026-05-22
Hasta la Fase 9 el censo de aplicaciones se fijaba en el arranque: las apps nacían del manifiesto y sólo morían al fallar. La Fase 10 lo vuelve DINÁMICO — una app puede nacer o cerrarse con el reactor ya en marcha.
Añadido
- El reactor admite nacimientos en vivo.
executor: una cola de NACIMIENTOS (Vec<FuturoTarea>tras unMutex) y la funciónengendrar. El ejecutor la drena al inicio de cada vuelta (recoger_nacimientos) y adopta cada futuro como tarea.Task::adoptaracoge un futuro ya empaquetado.dormir_si_inactivocuenta los nacimientos como trabajo. - Baja en vivo —
Alt+Q. MandoCerrar: el compositor marca la ventana enfocada comocerrada, libera su caché de respaldo, la saca del teselado y del orden-Z, y traslada el foco a una ventana viva contigua. La app, en su tarea, consultacompositor::ventana_cerradacada fotograma; al verla cerrada concluye su tarea — yAplicacionWasm::droplibera su memoria lineal, su combustible y su canal de teclado. Una baja LIMPIA, sin baliza. - Alta en vivo —
Alt+N. MandoLanzar: el compositor cuenta la petición (PARTOS); la tarea del compositor la atiende conpartos_pendientesylanzar_app.compositor::nacer_ventanaañade la ventana y devuelve su índice; el orquestador instancia el WASM con ese índice yengendrasu tarea. Las apps de génesis dejan su bytecode cacheado en RAM comoPlantilla; cadaAlt+Ninstancia la siguiente en rotación, sin volver al disco —una E/S por sondeo en mitad del reactor sería un mal vecino—. compositor: campoVentana.cerrada, mandosCerrar/Lanzar, y las funcionescerrar,nacer_ventana,ventana_cerrada,partos_pendientes.teclado:Alt+Q→Cerrar,Alt+N→Lanzar.
Cambiado
encender_appdevuelve laPlantillade la app —su bytecode y geometría— para los lanzamientos en vivo.tarea_aplicacionconsultaventana_cerradaantes de cadatick.presentar_fotogramaydesalojarignoran una ventana ya cerrada: una baja limpia gana a un fotograma o a un desalojo que lleguen tarde.
Verificado
- QEMU (
sendkey): tresAlt+Ndan a luz tres apps nuevas y el escritorio se re-tesela de 5 a 8 ventanas. TresAlt+Qcierran la app enfocada una a una y el teselado reclama su espacio, de 8 de vuelta a 5. El kernel sigue estable a través de todas las altas y bajas.
Fase 11 — El reloj del sistema como capacidad de host — 2026-05-22
Hasta la Fase 10 una aplicación sólo sabía CUÁNTAS veces la habían llamado —un
tick tras otro—, no CUÁNTO tiempo había pasado. La Fase 11 le da al userspace
un sentido del tiempo: el reloj monótono del sistema, como capacidad.
Añadido
- Capacidad
sys_tiempo_mono() -> u64— la décima función del host. Los milisegundos transcurridos desde el arranque. El temporizador (PIT) ya late a 100 Hz;relojexpone esa cuenta comomilisegundos()yenvla inyecta. Es una lectura PURA —no toca la memoria lineal del módulo, no hay puntero que validar— y MONÓTONA: jamás retrocede. - App
pulso(apps/pulso/,wasm32). Un compás visual: una cabeza brillante que recorre una pista y vuelve, con un período de 6 s. Su escena es una FUNCIÓN PURA desys_tiempo_mono—no guarda estado entre fotogramas—. De ahí su prueba: dos instancias depulso, nazca una al arrancar y otra mucho después con unAlt+N, laten exactamente al unísono. pulsose suma al userspace de génesis:GENESISpasa de 5 a 6 apps, conpulsocomo la primera —la ventana maestra del escritorio—.
Verificado
- QEMU (
sendkey): la barra depulsoavanza con el tiempo de pared entre dos capturas. UnAlt+Nda a luz un segundopulso~15 s después del arranque; flotado junto al primero, su barra está en la MISMA fase — la prueba de que el compás se rige por el reloj absoluto, no por una cuenta de fotogramas.