Files
sergio 42fee6fcbc feat(renaser): Fase 20 — Akasha Over Ether (grafo distribuido)
Tres mensajes y un EtherType propio bastan para extender el grafo de
objetos —direccionado por contenido, ya BLAKE3— a otras maquinas
renaser que escuchen en la misma red de capa-2. Sin TCP, sin IP,
sin DNS.

Crate nueva 'akasha/' (no_std compartido, gemela de 'formato',
excluida del workspace):

  - MensajeAkasha enum con SolicitarObjeto(id), ProveedorObjeto(id,
    payload), AnunciarRaiz(id).
  - Codec: postcard (mismo que ya usa el grafo en disco).
  - EtherType: 0x88B5. MAX_PAYLOAD_AKASHA = 1486 (MTU sin fragmentar).
  - Helpers componer_frame(src, dst, msg) y analizar_frame(bytes) que
    distinguen EtherType ajeno, frame truncado y payload basura.
  - 6 pruebas unitarias en verde.

Modulo nuevo 'kernel/src/akasha.rs' con tres oficios:

  1. Demuxer (drenar_y_demultiplexar): drena la cola RX del dispositivo
     virtio-net y demultiplexa: frames AoE con payload valido los
     procesa el respondedor; el resto va a una cola del userspace que
     'sys_net_recibir' ahora lee. Frames 0x88B5 con payload
     no-postcard (saludo de pregon) se cuentan y tambien viajan al
     userspace.

  2. Atencion de mensajes (procesar):
     - SolicitarObjeto(id): consulta almacen::recuperar; si tenemos el
       objeto, respondemos ProveedorObjeto unicast con objeto.serializar()
       y re-hashing de defensa en profundidad.
     - ProveedorObjeto(id, payload): verifica blake3(payload)==id antes
       de absorber con almacen::almacenar.
     - AnunciarRaiz(id): si ignoramos el nodo, le solicitamos al emisor.

  3. Faro periodico (difundir_raiz cada 5 s): broadcast del hash del
     manifiesto actual. Cadencia medida contra reloj::milisegundos(),
     no contra los awaits — el interprete wasmi de los apps degrada
     la cadencia de EsperaFrame::await a varios cientos de ms, asi
     que se mide contra el reloj monotono y los oficios per-fotograma
     se enganchan al tic del compositor (cuyo latido es fiable).

Contadores ResumenAkasha (rx/tx por variante, descartados, cola del
usuario) listos para un futuro indicador AoE en la barra de tareas.

Cambios complementarios:

  - sys_net_recibir lee de akasha::pop_usuario, no de
    drivers::red::recibir_en (que queda #[allow(dead_code)] como
    primitiva del driver para diagnostico).
  - tarea_red queda corta: envia un ARP al gateway y termina. El
    demuxer y el faro viven en el tic del compositor.

Verificacion:

  - 'cargo test -p akasha' → 6 pruebas en verde.
  - QEMU headless 60 s con -object filter-dump → 14 frames: 11
    AnunciarRaiz (Δ promedio 5.86 s sobre 5.00 s de target), 2 ARP
    y el pregon hello. Cada AnunciarRaiz lleva el hash del manifiesto
    '2f3deadfcc7dae25..' en 33 bytes postcard sobre 47 bytes de frame.
  - COM1 vuelca 'akasha :: ANUNCIO emitido :: raiz=2f3deadfcc7dae25..'
    en cada disparo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 05:14:43 +00:00

70 KiB
Raw Permalink Blame History

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 tickFUEL_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.rssys_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_discodisco::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 virgenboot 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 arranqueboot 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.
    • Reinicioboot 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.

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. Enlaza mirada-layout —el mismo núcleo no_std que ordena las ventanas del compositor Wayland de brahman, ya enlazado por path cruzando la frontera de workspace— y, con su algoritmo MasterStack, 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 color Color::PANEL.

Cambiado

  • consola::volcar_marco ya 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.
  • ContextoCapacidades lleva ahora marco (el rectángulo teselado) y natural_ancho/natural_alto (el tamaño del lienzo de la app); sys_render_frame valida el fotograma contra el tamaño natural.
  • AplicacionWasm / encender_app / cargar_userspace cablean el marco teselado. cargar_userspace recibe las dimensiones de pantalla, tesela con el compositor y pinta el escenario antes de encender las apps.
  • RegionPantalla::pixeles se retira — sin uso desde que sys_render_frame mide 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 MasterStackhola como 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 registro ESCRITORIO —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 un AtomicUsize; 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) y Color::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+Espacio cicla el modo de teselado; Alt+J / Alt+K mueven 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.
  • consola::volcar_marco y pintar_desalojo trazan el borde de foco de la ventana. sys_render_frame ya no compone directo: delega en compositor::presentar_fotograma, que cachea antes de componer.
  • AplicacionWasm / ContextoCapacidades pierden el marco —ahora vive en el registro del compositor, mutable—; conservan sólo el indice_app.

Exclusión de interrupciones (guardarraíl)

  • El registro ESCRITORIO lo 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 + sendkey por el monitor):
    • Arranque — cinco apps teseladas en MasterStack; hola enfocada luce el borde índigo, las demás el gris.
    • Alt+Espacio — el escritorio cicla a CenteredMaster; 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 de hola a memoriosa; el borde índigo se mueve con él.
    • Enrutamiento — con memoriosa enfocada, cuatro pulsaciones llegan sólo a ella —cuatro celdas violetas—; las demás apps, intactas.

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

  • Escritorio gana orden: 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_foco recorre ahora el ORDEN de teselado —no los índices crudos—: el foco salta entre ventanas visualmente contiguas.
  • ciclar_layout y fundar delegan en aplicar_teselado.

Verificado

  • QEMU (sendkey): con memoriosa enfocada, Alt+Enter la promueve a la ventana maestra y hola baja a la pila; Alt+L la devuelve a la pila y hola recupera 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

  • Escritorio gana flotantes: Vec<usize> — la pila de ventanas flotantes en orden-Z, de atrás hacia adelante; flotantes.last() es la frontal. Junto con orden forma 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::recomponer y los tipos Capa / 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 de volcar_marco para compartirlo entre el camino rápido y la recomposición.

Cambiado

  • presentar_fotograma y desalojar bifurcan: 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_foco recorre 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, promover y mover_ventana delegan el repintado en recomponer; desaparecen redibujar_todo, redibujar_ventana y pintar_escenario.

Verificado

  • QEMU (sendkey): Alt+F saca la ventana maestra del teselado y la deja flotando sobre las demás, que se re-teselan. Un segundo Alt+F sobre otra ventana la flota en cascada, solapando a la primera. Alt+K devuelve el foco a la ventana grande y ésta sube al frente, tapando por completo a la pequeña. Un Alt+F final 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 un Mutex) y la función engendrar. El ejecutor la drena al inicio de cada vuelta (recoger_nacimientos) y adopta cada futuro como tarea. Task::adoptar acoge un futuro ya empaquetado. dormir_si_inactivo cuenta los nacimientos como trabajo.
  • Baja en vivo — Alt+Q. Mando Cerrar: el compositor marca la ventana enfocada como cerrada, 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, consulta compositor::ventana_cerrada cada fotograma; al verla cerrada concluye su tarea — y AplicacionWasm::drop libera su memoria lineal, su combustible y su canal de teclado. Una baja LIMPIA, sin baliza.
  • Alta en vivo — Alt+N. Mando Lanzar: el compositor cuenta la petición (PARTOS); la tarea del compositor la atiende con partos_pendientes y lanzar_app. compositor::nacer_ventana añade la ventana y devuelve su índice; el orquestador instancia el WASM con ese índice y engendra su tarea. Las apps de génesis dejan su bytecode cacheado en RAM como Plantilla; cada Alt+N instancia la siguiente en rotación, sin volver al disco —una E/S por sondeo en mitad del reactor sería un mal vecino—.
  • compositor: campo Ventana.cerrada, mandos Cerrar / Lanzar, y las funciones cerrar, nacer_ventana, ventana_cerrada, partos_pendientes.
  • teclado: Alt+QCerrar, Alt+NLanzar.

Cambiado

  • encender_app devuelve la Plantilla de la app —su bytecode y geometría— para los lanzamientos en vivo.
  • tarea_aplicacion consulta ventana_cerrada antes de cada tick.
  • presentar_fotograma y desalojar ignoran una ventana ya cerrada: una baja limpia gana a un fotograma o a un desalojo que lleguen tarde.

Verificado

  • QEMU (sendkey): tres Alt+N dan a luz tres apps nuevas y el escritorio se re-tesela de 5 a 8 ventanas. Tres Alt+Q cierran 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; reloj expone esa cuenta como milisegundos() y env la 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 de sys_tiempo_mono —no guarda estado entre fotogramas—. De ahí su prueba: dos instancias de pulso, nazca una al arrancar y otra mucho después con un Alt+N, laten exactamente al unísono.
  • pulso se suma al userspace de génesis: GENESIS pasa de 5 a 6 apps, con pulso como la primera —la ventana maestra del escritorio—.

Verificado

  • QEMU (sendkey): la barra de pulso avanza con el tiempo de pared entre dos capturas. Un Alt+N da a luz un segundo pulso ~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.

Fase 12 — La bocina del PC como capacidad de host — 2026-05-22

La Fase 11 le dio al userspace un reloj; la Fase 12, una voz. La bocina del PC —el canal 2 del PIT como generador de onda cuadrada— se suma a la matriz de capacidades. Hasta hoy renaser sólo sabía DIBUJAR para llamar la atención.

Añadido

  • Driver drivers/altavoz. Programa el canal 2 del PIT a una frecuencia y abre la compuerta del puerto 0x61. El canal 0 del PIT es el latido del kernel; el canal 2 es de la bocina y de nadie más — no se perturban—. tono(hz) es su única vía; un 0 la silencia.
  • Capacidad sys_tono(frecuencia_hz) — la undécima función del host. La bocina es un recurso ÚNICO y global: para que dos apps no se la disputen, pertenece —como el teclado desde la Fase 8c— a la ventana ENFOCADA. Una app sin foco puede pedir un tono; sencillamente, no se oye. Al cambiar el foco, el compositor calla la bocina (mover_foco, cerrar): la nueva dueña la reclama en su próximo fotograma.
  • App tonada (apps/tonada/, wasm32). Toca una escala de Do mayor en bucle y la dibuja como una escalera de ocho barras, con la nota que suena encendida. Junta las dos capacidades nuevas del kernel: el reloj (sys_tiempo_mono) le marca el compás, la bocina (sys_tono) le da voz.
  • tonada encabeza el userspace de génesis: GENESIS pasa de 6 a 7 apps, con tonada como la ventana maestra —enfocada al arrancar, así suena de inmediato—.

Verificado

  • QEMU. Visual: la escalera de tonada enciende la nota en curso y ésta recorre la escala con el tiempo (capturas con la 6.ª y la 1.ª nota vivas). Sonido: con la bocina enrutada a un WAV (-audiodev wav + -machine pcspk-audiodev), el PCM capturado —50 s— es una onda cuadrada oscilante de ±24 000 de amplitud, con una frecuencia media de ~375 Hz: la de la escala de Do mayor.

Fase 13 — Ratón, puntero y arrastre de flotantes — 2026-05-22

renaser tenía teclado y bocina como entrada/salida del usuario, pero no puntero. Las ventanas flotantes de la Fase 9 nacían en cascada y se quedaban ahí, clavadas. La Fase 13 trae el ratón: un puntero en pantalla, clic-para- enfocar y arrastre del marco de las ventanas flotantes.

Añadido

  • Driver drivers/raton — el ratón PS/2 cuelga del dispositivo auxiliar del 8042 (la misma controladora que el teclado) y anuncia cada movimiento por la IRQ12. El driver despierta el aux, enciende su IRQ, le ordena reportar, y ensambla los paquetes de 3 bytes con la guarda del bit-3 que detecta desincronización. Como el teclado, la IRQ12 sólo toca atómicos —posición del puntero— y una cola lock-free de eventos.
  • El puntero, capa de presentación. Pantalla gana formato y la función estampar_puntero: un sprite de flecha de 12×18 que se pinta DIRECTAMENTE sobre el framebuffer, justo después de copiar el lienzo. El lienzo permanece libre de puntero —hace de save-under natural—; cada presentar sella el puntero al final.
  • Capacidad del compositor: atender_raton drena eventos del ratón cada fotograma. El botón izquierdo bajando es un CLIC: enfoca la ventana viva bajo el puntero (consistente con mover_foco — silencia la bocina, alza al frente si flota). Si la enfocada es flotante, arranca un ARRASTRE con el desfase de agarre; con el botón sostenido, la ventana sigue al puntero; soltarlo lo termina.
  • refrescar_puntero: en una vuelta tranquila en que ninguna app pinte, reestampa el framebuffer si el puntero se ha movido — el centinela empacado evita repintar dos veces el mismo instante.
  • Escritorio gana arrastre: Option<Arrastre> y raton_izq: bool. Arrastre guarda el índice de la ventana asida y el desfase de agarre. cerrar libera el arrastre si la ventana cerrada era la arrastrada.

Verificado

  • QEMU (mouse_move + mouse_button del monitor). El puntero aparece en el centro al arrancar; mouse_move lo lleva por la pantalla. Clic sobre la ventana pulso (en la pila) la enfoca: el borde índigo deja la maestra y envuelve a pulso. Alt+F la flota; el ratón la agarra y la arrastra de la esquina superior izquierda al centro-abajo de la pantalla con el botón sostenido. El kernel sigue estable a través de los gestos.

Mapeador MMIO en el kernel — 2026-05-23

OVMF aloja los BAR prefetchables 64-bit de virtio en la ventana PCI de 64 bits, que el bootloader_api no mapea. KernelHal::mmio_phys_to_virt devolvía phys + offset a ciegas y el primer registro MMIO leído reventaba con #PF, dejando una franja roja sin pista visible. La autopsia: el commit anterior añadió el dump del panic-handler por COM1, que sacó a la luz el IP en unchecked_mul::precondition_check y la dirección de fallo offset + 0x800000014 — phys 32 GiB, donde OVMF había puesto el BAR.

Añadido

  • memory::mmio: envuelve la tabla L4 activa (vía Cr3 + el mapeo de memoria física del cargador) en un OffsetPageTable. mapear(fisica, tam) abre páginas hacia la región pedida con PRESENT | WRITABLE | NO_CACHE | WRITE_THROUGH, tratando PageAlreadyMapped y ParentEntryHugePage como éxito silencioso.
  • Los marcos para tablas intermedias salen del banco DMA del disco (asignar_marco_para_tabla, sin pánico).
  • KernelHal::mmio_phys_to_virt llama a memory::mmio::mapear antes de devolver el puntero, asegurándose de que el BAR sea accesible.

Cambiado

  • baliza::Serie es ahora pub(crate): cualquier módulo puede dejar trazas por COM1. El panic-handler y el OOM-handler dejan en COM1 su mensaje y ubicación además de pintar la franja. kernel_main traza cada hito del arranque para diagnóstico remoto.

Fase 14 — La identidad del escritorio: nombres y barra de tareas — 2026-05-23

Las ventanas eran anónimas: el escritorio reía con sus colores pero no sabía cómo se llama lo que muestra. La Fase 14 le da nombre a cada cuarto y una barra al pie con la lista de quien vive en la casa.

Añadido

  • Cada Ventana lleva un nombre: String —el que dicta su EntradaApp del manifiesto, o el que el orquestador pasa al engendrarla en vivo (Fase 10)—. Plantilla se acuerda del nombre para los lanzamientos posteriores.
  • FRANJA_TASKBAR (40 px al pie) y area_taskbar: la barra de tareas vive en su propia franja. area_apps la descuenta — las ventanas teselan y flotan SIN tapar la barra ni la consola de la cima.
  • consola: tipos Taskbar y CeldaTaskbar; métodos pintar_taskbar (fondo + línea divisoria + una pestaña por ventana viva) y pintar_etiqueta (rasteriza una cadena en un punto fijo, sin tocar la pluma de la consola). La pestaña enfocada lleva el índigo del foco; las desalojadas, su color de baliza; las demás, el slate del panel.
  • compositor::recomponer arma la lista de celdas y la pasa a la consola junto con las capas: un único repintado, una única presentación.
  • atender_raton: un clic dentro de la franja de la barra busca su pestaña con celda_taskbar_en y enfoca la ventana correspondiente (sin iniciar arrastre). El comportamiento sobre el área de apps queda intacto.

Cambiado

  • compositor::fundar y nacer_ventana exigen un nombre para cada ventana. cargar_userspace lo toma de EntradaApp.nombre; lanzar_app, de Plantilla.nombre.

Verificado

  • QEMU. Tras arrancar se ve la barra al pie con las 7 pestañas (tonada enfocada, índigo), las desalojadas en sus colores de baliza. Un clic sobre pulso cambia el foco al instante: el borde índigo del compositor deja la maestra y envuelve a pulso, y la pestaña pulso se ilumina.

Fase 15 — La voz del sistema — 2026-05-23

La bocina pertenecía a la ventana enfocada (Fase 12), pero el kernel necesita hablar también: un acorde al abrir la casa, un repique al recibir un inquilino nuevo, un bajo al echar a uno que se rompió. La Fase 15 le da al sistema su propia voz, prioritaria sobre las de los apps.

Añadido

  • altavoz::SECUENCIA — una VecDeque<(u32, u32)> (frecuencia, duración ms) que el kernel encola con altavoz::agendar(&[(...)]). FIN_NOTA (un AtomicU64) recuerda el milisegundo del reloj monótono en que la nota actual debe terminar.
  • altavoz::atender() — invocada por la tarea del compositor cada fotograma; si la nota actual ya terminó, saca la siguiente de la cola y la toca; si la cola está vacía, silencia.
  • altavoz::kernel_sonando()true mientras FIN_NOTA esté en el futuro. sys_tono lo consulta y, en ese caso, ignora la llamada del app: el kernel no se interrumpe a sí mismo.
  • Catálogo de voces: VOZ_BIENVENIDA (Do5-Mi5-Sol5 ascendente, 500 ms), VOZ_LANZAR (repique 700→1050 Hz), VOZ_CERRAR (descendente 900→520 Hz), VOZ_DESALOJO (bajo 180 Hz, 260 ms).
  • Hitos sonoros: kernel_main agenda VOZ_BIENVENIDA justo antes de ejecutor.run(). nacer_ventana agenda VOZ_LANZAR. cerrar agenda VOZ_CERRAR. desalojar agenda VOZ_DESALOJO.

Cambiado

  • Las pestañas de la barra de tareas calculan su tinta por brillo del fondo (ITU-R BT.601): la pestaña amarilla pálida del desalojo por memoria, que llevaba texto blanco invisible, ahora luce su nombre en tinta oscura.

Verificado

  • QEMU con -audiodev wav -machine pcspk-audiodev=spk. El PCM crudo revela, en orden, las tres notas del acorde de bienvenida (≈520, 630, 760 Hz), inmediatamente un brevísimo bajo de 180 Hz (la baliza de discola/glotona desalojadas), y después la escala de tonada tomando la bocina.
  • Captura: la pestaña de glotona (crema) muestra ahora su nombre legible en tinta oscura; la de discola (púrpura) sigue clara, como antes.

Fase 16 — La barra viva: lanzador y reloj — 2026-05-23

La barra de tareas era informativa pero pasiva: nombraba a los inquilinos sin ofrecerse a ningún gesto propio. La Fase 16 le añade los dos accesorios de toda barra de tareas digna: un botón lanzador a la izquierda y un reloj a la derecha, y le pone el reloj a latir cada segundo.

Añadido

  • Botón lanzador («+») a la izquierda de la barra (36 px de ancho, fondo índigo del foco, cruz blanca). Un clic incrementa PARTOS — el mismo contador que Alt+N —, así que la tarea del compositor lo recoge y lanza la siguiente app de la rotación. El teclado y el ratón comparten ahora la misma vía para crear ventanas.
  • Reloj a la derecha de la barra (80 px), formato mm:ss desde el arranque, leído de reloj::milisegundos(). Tinta blanca sobre slate.
  • compositor::tick_reloj() — la invoca la tarea del compositor cada fotograma. Si el segundo del reloj cambió respecto al último mostrado (ULTIMO_SEGUNDO: AtomicU64), recompone; si no, vuelve. Cero coste cuando no hace falta refrescar.
  • Taskbar gana los campos launcher: RegionPantalla, reloj: &str y reloj_region: RegionPantalla. pintar_taskbar los pinta como dos cuñas fijas de la barra: el lanzador con su cruz dibujada en píxeles directos (independiente de la tipografía), el reloj con la etiqueta de costumbre.

Cambiado

  • El layout de la barra se recalcula: cells_x0 empieza tras el lanzador, cells_x_max termina antes del reloj. celda_taskbar_en respeta esos límites; un clic en el lanzador (clic_en_launcher) tiene su propia rama en atender_raton, antes que la búsqueda de pestaña.
  • CELDA_TASKBAR_ANCHO baja de 156 a 150 para que el lanzador, las siete pestañas y el reloj quepan holgados en una pantalla de 1280 píxeles.

Verificado

  • QEMU. La barra al arrancar muestra el botón «+» indigo a la izquierda, las siete pestañas (con glotona ya legible en tinta oscura sobre crema), y el reloj 0:17 a la derecha (el tiempo que el kernel lleva vivo al capturar). Diez segundos después, el reloj marca 0:29 — la barra se ha refrescado doce veces sin intervención del ratón ni del teclado.

Fase 17 — bitacora :: el editor que recuerda — 2026-05-23

memoriosa (Fase 7c) demostró que un app podía persistir un contador a través de los reinicios. bitacora extiende esa demostración a un EDITOR de texto: tecleas, los caracteres aparecen; reinicias el kernel, el texto sigue ahí. La huella vive en el grafo de objetos, como todo lo demás.

Añadido — app bitacora (apps/bitacora/)

  • Lienzo 480×280 con título "bitacora :: el texto persiste" en índigo arriba y, debajo, las últimas líneas del buffer. Wrap automático a 28 columnas; Enter salta de línea; Backspace borra el último carácter.
  • Tipografía 8×8 embebida (crate font8x8 = "0.3"), escalada x2 a 16×16. El app rasteriza cada glifo pixel a pixel desde su propia memoria lineal.
  • Persistencia automática: cada cambio invoca sys_estado_guardar. Al arrancar, init llama a sys_estado_cargar y restaura el buffer.
  • Buffer de 512 bytes; al desbordarse descarta los 64 primeros para hacer hueco (amortiza el coste — no es una mudanza por cada pulsación).
  • Mapeo de scancodes US a ASCII para letras, dígitos y puntuación común; Enter genera \n, Backspace borra. Sin mayúsculas ni modificadores.

Cambiado

  • GENESIS crece de 7 a 8 apps con bitacora como la PRIMERA — gana la celda maestra al arrancar, así que la primera ventana grande del escritorio te invita a teclear.
  • CELDA_TASKBAR_ANCHO baja de 150 a 130 píxeles para que las ocho pestañas + el lanzador + el reloj quepan holgadas en 1280 px.

Verificado

  • QEMU (sendkey del monitor): tras escribir hola renaser y quit → relanzar QEMU con el mismo disk.img, la bitacora muestra de nuevo el texto justo donde quedó. El almacen reporta 24 objetos en el grafo (frente a 9 antes de escribir) y raiz presente: cada guardar anexó una versión al log direccionado por contenido.

Fase 18 — Red: virtio-net y el primer hola al exterior — 2026-05-23

renaser hablaba consigo mismo. Con la misma plantilla del disco —enumerar PCI, montar el transporte virtio, ceder a virtio-drivers el diálogo de bajo nivel— y reutilizando el KernelHal y el mapeador memory::mmio, el kernel abre ahora una boca y una oreja al exterior: una tarjeta de red.

Añadido

  • Driver drivers/red — monta VirtIONet<KernelHal, PciTransport, 16>, enruta su IRQ vía el PIC, expone enviar(frame) y drenar_rx(callback). La cabeza del archivo guarda el MAC del dispositivo y las constantes de IP de QEMU user-mode (10.0.2.15 invitado, 10.0.2.2 gateway).
  • componer_arp_request(mac, ip, objetivo) — construye un frame Ethernet + ARP listo para enviar.
  • interrupts::registrar_irq_red + extern "x86-interrupt" irq_red — mismo patrón que la IRQ del disco. Acknowledge en el dispositivo, EOI al PIC.
  • KernelHal se hace pub para que red lo reutilice como su Hal de DMA — la misma arena de marcos físicos que el disco. El mapeador MMIO (Fase 13.5) cubre los BAR del nuevo dispositivo sin tocar nada.
  • tarea_red: tras 10 fotogramas para que la cola RX se estabilice, envía un ARP request al gateway de QEMU. Después, cada fotograma drena la cola RX y vuelca cada paquete a COM1.
  • QEMU args: -netdev user,id=net0 -device virtio-net-pci,netdev=net0 (user-mode networking, NAT virtual al host).

Verificado

  • QEMU con -object filter-dump,...,file=/tmp/red.pcap. Trazas COM1:
    red :: virtio-net :: MAC 52:54:00:12:34:56 :: IRQ Some(11)
    red :: ARP REQUEST enviado :: ¿quien tiene 10.0.2.2?
    red :: RX 64 bytes :: dst=52:54:00:12:34:56 src=52:55:0a:00:02:02 type=0x0806
    
    El src del paquete entrante codifica la IP del gateway dentro del MAC (52:55:0a:00:02:02 = QEMU prefix + 10.0.2.2): la respuesta ARP es legítima. Renaser ya habla con la red.

Fase 19 — Voz del userspace hacia la red — 2026-05-23

La Fase 18 hizo que el kernel hablara con la red. La Fase 19 le da la misma capacidad a los apps: tres capacidades nuevas (sys_net_mac, sys_net_enviar, sys_net_recibir) y una primera app que las usa.

Añadido

  • drivers::red::recibir_en(buf) -> usize — recibe UN paquete por llamada, lo copia al buffer del llamante, recicla el descriptor a la cola RX. Devuelve 0 si no hay paquete pendiente. El gemelo cooperativo de drenar_rx, pensado para la interfaz que el host expone a los apps.
  • Tres capacidades nuevas en wasm/env (índices 12, 13, 14):
    • sys_net_mac(salida: u32) -> i32 — escribe seis bytes con la MAC del dispositivo en (salida, 6). Devuelve 0 OK o -1 si no hay red.
    • sys_net_enviar(ptr: u32, len: u32) -> i32 — envía el frame Ethernet crudo. Devuelve 0 OK, -1 red caída, -2 longitud inválida, -3 rango fuera de la memoria lineal.
    • sys_net_recibir(salida: u32, capacidad: u32) -> i32 — drena UN paquete (si hay) hacia (salida, capacidad). Devuelve los bytes copiados, 0 si nada pendiente, -1 si no hay red, -2 si la capacidad excede la memoria lineal. Todas validan rango() contra la memoria del módulo y registran diagnóstico por COM1 si falla la validación.
  • App pregon (apps/pregon/, 4.2 KiB WASM) — la primera voz del userspace hacia la red. Al arrancar:
    1. Pide su MAC al host vía sys_net_mac.
    2. Si la tiene, anuncia su presencia con un broadcast Ethernet (destino FF:FF:FF:FF:FF:FF, EtherType experimental 0x88B5, payload ASCII "renaser :: hola desde mi red" — 42 bytes en el cable). En cada tick drena un paquete con sys_net_recibir y pinta: el título, el MAC propio, las cuentas TX/RX, y los datos del último frame entrante (tamaño, EtherType, src MAC) — todo en tipografía 8×8 embebida (crate font8x8), escalada x2 sobre un lienzo 480×160 índigo.
  • pregon en el manifiesto de genesis (boot/src/main.rs): la novena app, posición 2 (detrás de bitacora). El disco se siembra ahora con 9 entradas; el kernel reporta manifiesto :: 9 apps nacidas del grafo.

Cambiado

  • tarea_red del kernel ya no drena la cola RX: la posesión de la cola pasa al userspace vía sys_net_recibir. La tarea conserva el envío del ARP de prueba al gateway en cuanto el dispositivo se estabiliza, y termina.
  • CELDA_TASKBAR_ANCHO baja de 130 a 116 px para que las nueve pestañas + lanzador + reloj caben holgadas en 1280 px.
  • ETHER_TYPE_RENASER del driver pasa a #[allow(dead_code)] — ahora vive en el userspace (en pregon); el kernel la conserva como referencia para futuras pilas nativas.

Verificado

  • QEMU headless con -object filter-dump,id=fd,netdev=net0,file=/tmp/renaser.pcap. El pcap captura tres frames en orden:
    #1 incl=42 dst=ff:ff:ff:ff:ff:ff src=52:54:00:12:34:56 etype=0x88b5
       payload: 'renaser :: hola desde mi red'
    #2 incl=42 dst=ff:ff:ff:ff:ff:ff src=52:54:00:12:34:56 etype=0x0806 (ARP REQ)
    #3 incl=64 dst=52:54:00:12:34:56 src=52:55:0a:00:02:02 etype=0x0806 (ARP REP)
    
    El primero es pregon; el segundo el kernel; el tercero es el gateway respondiéndonos — el ciclo completo de salida y entrada en una sola fase.
  • Captura de pantalla: la ventana pregon :: voz hacia la red aparece teselada, su pestaña sale en la barra de tareas entre bitacora y tonada, y la consola anuncia manifiesto :: 9 apps nacidas del grafo.

Fase 20 — Akasha Over Ether: el grafo distribuido — 2026-05-23

La red dejo de ser un puro medio para mensajes ad-hoc y se vuelve el cable por donde el GRAFO de objetos se extiende a otras maquinas renaser. Tres mensajes bastan; postcard los serializa; el EtherType 0x88B5 los transporta; ningun byte de TCP, IP, DNS o cabecera transporte de los anos 80.

Añadido

  • Crate akasha/ — nucleo #![no_std] compartido (gemela de formato), excluida del workspace anfitrion. Define MensajeAkasha con tres variantes —SolicitarObjeto(id), ProveedorObjeto(id, payload), AnunciarRaiz(id)—, las helpers componer_frame(src, dst, msg) y analizar_frame(bytes) -> Result<(Mac, MensajeAkasha)>, y las constantes ETHER_TYPE_AKASHA = 0x88B5, MAX_PAYLOAD_AKASHA = 1486. Seis pruebas unitarias cubren el ida-y-vuelta de cada variante, la distincion entre EtherType ajeno, frame truncado y payload basura.
  • Modulo kernel/src/akasha.rs — el respondedor del nucleo. Tres oficios:
    1. Demuxer (drenar_y_demultiplexar): drena la cola RX del dispositivo virtio-net y demultiplexa: frames AoE con payload valido los procesa procesar; el resto va a la cola del userspace.
    2. Atencion de mensajes (procesar): SolicitarObjeto busca en almacen y, si tiene el objeto, responde ProveedorObjeto unicast; ProveedorObjeto verifica integridad (blake3(payload) == id) y absorbe al grafo local con almacen::almacenar; AnunciarRaiz se contabiliza y, si no tenemos el nodo, le solicita al emisor.
    3. Faro (difundir_raiz cada 5 s): difunde por broadcast el hash del manifiesto actual — el faro Akasha que delata nuestra presencia en la red de capa-2.
  • Cola del userspace — el demuxer empuja los frames no-AoE a una VecDeque<Vec<u8>> con profundidad maxima 64; sys_net_recibir ahora pasa por akasha::pop_usuario(buf) en lugar de leer directamente del driver. Asi el kernel filtra Akasha sin disputarle paquetes al userspace.
  • tic_compositor — el punto de entrada de los oficios per-fotograma de AoE, llamado desde el tic del compositor (cuyo latido es fiable). El demuxer corre en cada vuelta; el faro se dispara cuando el reloj monotono cruza INTERVALO_FARO_MS (5 s).
  • Contadores (ResumenAkasha): rx_solicitudes, rx_proveedores, rx_anuncios, tx_* equivalentes, rx_descartados, usuario_encolados, usuario_desbordados. Quedaran de fundamento para un futuro indicador AoE en la barra de tareas.

Cambiado

  • tarea_red vuelve a su rol simple: enviar UN ARP request al gateway de QEMU al arrancar. El demuxer y el faro pasaron al tic del compositor — cuyo latido cooperativo es el unico GARANTIZADO bajo carga del reactor. Un bucle propio en tarea_red (intentado primero) escala mal porque la cadencia de EsperaFrame::await se degrada cuando los apps WASM bajo wasmi ocupan el reactor (cada tic de app son decenas de ms de interprete); medirlo contra el reloj monotono y enganchar al compositor resuelve la falla con limpieza.
  • sys_net_recibir lee de la cola del userspace que mantiene akasha, no del driver. El bloque doc en wasm/env.rs lo refleja.
  • drivers::red::recibir_en queda como primitiva del driver con #[allow(dead_code)]: ya no la consume el sistema, pero la conservamos para futuras herramientas de diagnostico que quieran leer la cola RX cruda.
  • Demuxer politico: frames 0x88B5 con payload basura para postcard — por ejemplo el saludo en texto plano de pregon— se cuentan en RX_DESCARTADOS pero TAMBIEN viajan a la cola del userspace. Asi pregon o cualquier otro app que comparta el EtherType experimental pueda recibirlos sin que el kernel se los robe.

Verificado

  • cargo test -p akasha — seis pruebas en verde:
    test pruebas::anuncio_de_raiz_viaja_compacto ... ok
    test pruebas::componer_y_analizar_solicitud_es_simetrico ... ok
    test pruebas::ethertype_ajeno_se_distingue ... ok
    test pruebas::frame_demasiado_corto_se_distingue ... ok
    test pruebas::payload_invalido_se_distingue ... ok
    test pruebas::proveedor_lleva_payload_arbitrario ... ok
    
  • QEMU headless 60 s, -object filter-dump,file=/tmp/renaser-aoe.pcap. El pcap captura 14 frames:
    ARP: 2  (request del kernel + reply del gateway)
    pregon: 1  (saludo texto plano)
    AnunciarRaiz: 11  (faros del kernel, Δ promedio 5.86 s sobre 60 s)
    
    Cada AnunciarRaiz lleva como payload el hash del manifiesto: 2f3deadfcc7dae25.. (postcard de 33 bytes — variante + hash — sobre un frame Ethernet total de 47 bytes).
  • COM1 vuelca akasha :: ANUNCIO emitido :: raiz=2f3deadfcc7dae25.. por cada disparo; en una corrida de 60 s aparecen 11 anuncios consecutivos.