feat(cosmobiologia-corpus): tomografía por dominio + plantilla y guía
El corpus ya rebana la carta en tajadas vivenciales: una sola configuración mirada plano a plano, sin promediar la contradicción. - Colocacion / AspectoEnCarta: la posición real de un planeta en una carta — el puente entre el motor astronómico y las claves del JOIN. - combinaciones_de_carta: deriva todas las CombinacionId de una carta. - rebanar_por_dominio: la tomografía — cada planeta@cN cae en el dominio de su casa, cada planeta·signo hereda el de su casa, y un aspecto puentea apareciendo en las dos tajadas que conecta. - Corpus::interpretar_por_dominio: el JOIN agrupado por dominio, entrada directa del gráfico «por tajadas». - CombinacionId acepta el alias ASCII '/' del punto medio '·'. - ejemplo.ron: plantilla cargable y comentada del corpus. - GUIA.md: los pasos exactos para generar el corpus a mano. 12 tests verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+4
-1
@@ -15,7 +15,10 @@ resolver = "2"
|
||||
members = ["boot"]
|
||||
# El kernel (bare-metal) y las apps WASM (target wasm32) se compilan aparte,
|
||||
# cada cual con su propio target; quedan fuera del espacio de trabajo.
|
||||
exclude = ["kernel", "apps"]
|
||||
# `formato` —el formato del grafo en disco— tambien se excluye: es un nucleo
|
||||
# `no_std` que enlaza el kernel bare-metal, asi que se compila como dependencia
|
||||
# de cada lado (kernel y boot) y no como miembro del workspace anfitrion.
|
||||
exclude = ["kernel", "apps", "formato"]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Metadatos compartidos: cada miembro hereda esta identidad con `*.workspace`.
|
||||
|
||||
@@ -17,6 +17,12 @@ description = "renaser :: constructor de imagen de disco UEFI y lanzador de QEMU
|
||||
# Constructor de la imagen de disco UEFI. Corre en el anfitrion, usa `std`.
|
||||
bootloader.workspace = true
|
||||
|
||||
# El formato del grafo de objetos en disco (Fase 7b). Es el MISMO nucleo
|
||||
# `no_std` que enlaza el kernel: gracias a el, `boot` siembra la imagen de
|
||||
# disco con el grafo ya poblado —objetos de bytecode y Manifiesto de Genesis—
|
||||
# hablando byte a byte el idioma que el kernel leera.
|
||||
formato = { path = "../formato" }
|
||||
|
||||
# Dependencia de ARTEFACTO (RFC 3028). Cargo compila el kernel para
|
||||
# `x86_64-unknown-none` —en aislamiento total de arquitectura— y nos inyecta la
|
||||
# ruta de su ELF en la variable de entorno `CARGO_BIN_FILE_KERNEL_kernel`,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# =============================================================================
|
||||
# renaser :: formato — el formato del grafo de objetos en disco
|
||||
# -----------------------------------------------------------------------------
|
||||
# Nucleo `#![no_std]` COMPARTIDO: lo enlaza el kernel bare-metal (target
|
||||
# `x86_64-unknown-none`) y, por ser no_std, tambien lo compila sin friccion el
|
||||
# anfitrion `boot`. Es la unica verdad del formato del grafo —tipos,
|
||||
# (de)serializacion postcard, hash BLAKE3, trazado de registros—, de modo que
|
||||
# kernel y constructor de imagen hablen exactamente el mismo idioma de disco.
|
||||
#
|
||||
# Queda EXCLUIDO del espacio de trabajo (ver el Cargo.toml raiz), como el
|
||||
# kernel: lo consume un paquete bare-metal, asi que fija sus versiones de
|
||||
# forma explicita, sin herencia del workspace.
|
||||
# =============================================================================
|
||||
|
||||
[package]
|
||||
name = "formato"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
authors = ["JL Soltech <gerencia@jlsoltech.com>"]
|
||||
description = "renaser :: formato del grafo de objetos en disco — compartido kernel ↔ boot"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
# `serde` da el rasgo de (de)serializacion; `postcard` lo materializa en un
|
||||
# formato binario compacto — el que viaja al disco. Ambos `no_std`, sobre `alloc`.
|
||||
serde = { version = "1", default-features = false, features = ["alloc", "derive"] }
|
||||
postcard = { version = "1", default-features = false, features = ["alloc"] }
|
||||
# `blake3`: la funcion hash que da identidad a cada objeto. Se fuerza la
|
||||
# implementacion ESCALAR pura (`pure` + los cuatro `no_*`): el target del kernel
|
||||
# corre sin SSE, y un camino SIMD por deteccion en tiempo de ejecucion
|
||||
# ejecutaria instrucciones que la CPU, sin `CR4.OSFXSR`, rechazaria con un #UD.
|
||||
blake3 = { version = "1", default-features = false, features = [
|
||||
"pure", "no_sse2", "no_sse41", "no_avx2", "no_avx512",
|
||||
] }
|
||||
@@ -0,0 +1,286 @@
|
||||
// =============================================================================
|
||||
// renaser :: formato — el formato del grafo de objetos en disco
|
||||
// -----------------------------------------------------------------------------
|
||||
// Hasta la Fase 7a, el formato del grafo de objetos —el superbloque, los
|
||||
// registros del log, el manifiesto— vivia disperso entre `kernel/almacen.rs`
|
||||
// y `kernel/manifiesto.rs`. Lo conocia solo el kernel.
|
||||
//
|
||||
// La Fase 7b se lo entrega tambien a `boot`: el constructor de imagen de
|
||||
// ANFITRION debe sembrar el disco con el grafo ya poblado —los objetos de
|
||||
// bytecode y el Manifiesto de Genesis— para que el kernel jamas vuelva a
|
||||
// empotrar una sola app. Para ello, kernel y boot han de hablar EXACTAMENTE
|
||||
// el mismo formato: la misma serializacion, el mismo hash, el mismo trazado
|
||||
// de registros en el log.
|
||||
//
|
||||
// Esta crate es esa unica verdad. Es un nucleo `#![no_std]` —el kernel
|
||||
// bare-metal la enlaza— y, por ser no_std, el anfitrion `boot` la compila sin
|
||||
// friccion. Define los tipos del grafo, su (de)serializacion `postcard`, la
|
||||
// funcion hash BLAKE3 que da identidad a cada objeto y el trazado de un
|
||||
// registro en el log. Ni kernel ni boot vuelven a definir nada de esto.
|
||||
// =============================================================================
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// =============================================================================
|
||||
// Constantes del formato en disco
|
||||
// =============================================================================
|
||||
|
||||
/// Firma magica del superbloque — «RENASer GRaFo». Distingue un disco de
|
||||
/// renaser de uno virgen o ajeno.
|
||||
pub const MAGIA: [u8; 8] = *b"RENASGRF";
|
||||
|
||||
/// Version del formato del superbloque en disco. Un disco con otra version se
|
||||
/// reformatea al arrancar. v2 (Fase 7) — el superbloque porta el ancla
|
||||
/// `manifiesto`, gemela de `raiz`.
|
||||
pub const VERSION_SUPERBLOQUE: u32 = 2;
|
||||
|
||||
/// Version del formato del manifiesto serializado. Independiente de la del
|
||||
/// superbloque: el manifiesto es un objeto del grafo, no una estructura fija
|
||||
/// del disco.
|
||||
pub const VERSION_MANIFIESTO: u32 = 1;
|
||||
|
||||
/// Techo del tamaño de un objeto serializado: 1 MiB. Acota los buferes de E/S
|
||||
/// y permite descartar un registro corrupto sin leer un disparate.
|
||||
pub const MAX_OBJETO: usize = 1024 * 1024;
|
||||
|
||||
/// Tamaño de un sector del disco, en bytes. El log se traza en multiplos de
|
||||
/// esta unidad — la misma que expone el transporte virtio-blk.
|
||||
pub const TAM_SECTOR: usize = 512;
|
||||
|
||||
/// El identificador de un objeto: el hash BLAKE3 de su forma serializada. En
|
||||
/// un almacen direccionado por contenido, la identidad ES el contenido.
|
||||
pub type Hash = [u8; 32];
|
||||
|
||||
// =============================================================================
|
||||
// Los tipos del grafo
|
||||
// =============================================================================
|
||||
|
||||
/// Un objeto del grafo: una carga util opaca y las aristas que lo enlazan con
|
||||
/// otros objetos. Los `hijos` hacen del almacen un DAG —no un arbol—: un
|
||||
/// objeto puede ser hijo de muchos, y el direccionamiento por contenido
|
||||
/// garantiza que cada contenido distinto se guarda una sola vez.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Objeto {
|
||||
/// La carga util del objeto: bytes crudos, que nadie interpreta aqui.
|
||||
pub datos: Vec<u8>,
|
||||
/// Los hashes de los objetos hijos: las aristas salientes del DAG.
|
||||
pub hijos: Vec<Hash>,
|
||||
}
|
||||
|
||||
/// El superbloque: el sector 0 del disco. Ancla el grafo entero — dice por
|
||||
/// donde continua el log, cual es el objeto raiz y cual el manifiesto.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct SuperBloque {
|
||||
/// Firma magica: debe ser [`MAGIA`].
|
||||
pub magia: [u8; 8],
|
||||
/// Version del formato: debe ser [`VERSION_SUPERBLOQUE`].
|
||||
pub version: u32,
|
||||
/// Proximo sector libre del log — donde se anexara el siguiente objeto.
|
||||
pub cursor: u64,
|
||||
/// El objeto raiz del DAG: el punto de entrada que el userspace fija y lee.
|
||||
pub raiz: Option<Hash>,
|
||||
/// El Manifiesto de Genesis: el objeto que dicta que apps nacen del grafo
|
||||
/// al arrancar. Ancla del kernel, gemela de `raiz` (del userspace).
|
||||
pub manifiesto: Option<Hash>,
|
||||
}
|
||||
|
||||
/// El Manifiesto de Genesis: la lista de aplicaciones que el kernel instancia
|
||||
/// al arrancar. Vive como un objeto del grafo; el superbloque guarda su hash.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Manifiesto {
|
||||
/// Version del formato — debe ser [`VERSION_MANIFIESTO`].
|
||||
pub version: u32,
|
||||
/// Las aplicaciones del userspace, en orden de arranque.
|
||||
pub apps: Vec<EntradaApp>,
|
||||
}
|
||||
|
||||
/// Una entrada del manifiesto: una aplicacion del userspace y todo lo que el
|
||||
/// kernel necesita para darle vida — su bytecode, su ventana, su cuota de
|
||||
/// memoria y, si lo tuviera, su ultimo estado persistido.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct EntradaApp {
|
||||
/// Nombre legible — para los rotulos de la consola y la baliza.
|
||||
pub nombre: String,
|
||||
/// Hash del objeto del grafo que contiene el bytecode WASM de la app.
|
||||
pub bytecode: Hash,
|
||||
/// Sub-region del framebuffer asignada a la app. Campos de ancho fijo
|
||||
/// `u32` A PROPOSITO: esto es un formato EN DISCO. La `RegionPantalla` del
|
||||
/// kernel usa `usize` (ancho dependiente de plataforma) y no serializa.
|
||||
pub region_x: u32,
|
||||
pub region_y: u32,
|
||||
pub region_ancho: u32,
|
||||
pub region_alto: u32,
|
||||
/// Techo de memoria lineal de la app, en bytes. Cada app lleva su cuota.
|
||||
pub techo_memoria: u32,
|
||||
/// Hash del ultimo estado persistido de la app (Fase 7c). `None` hasta que
|
||||
/// la app guarde estado por primera vez.
|
||||
pub estado: Option<Hash>,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// (De)serializacion — la forma binaria que viaja al disco
|
||||
// =============================================================================
|
||||
|
||||
impl Objeto {
|
||||
/// Serializa el objeto a su forma binaria `postcard`.
|
||||
pub fn serializar(&self) -> Result<Vec<u8>, &'static str> {
|
||||
postcard::to_allocvec(self).map_err(|_| "objeto :: serializacion fallida")
|
||||
}
|
||||
|
||||
/// Reconstruye un objeto desde su forma binaria. Tolera bytes sobrantes
|
||||
/// tras el objeto —el relleno del registro—: solo consume su prefijo.
|
||||
pub fn deserializar(bytes: &[u8]) -> Result<Objeto, &'static str> {
|
||||
postcard::take_from_bytes::<Objeto>(bytes)
|
||||
.map(|(objeto, _)| objeto)
|
||||
.map_err(|_| "objeto :: deserializacion fallida")
|
||||
}
|
||||
}
|
||||
|
||||
impl SuperBloque {
|
||||
/// Serializa el superbloque a su forma binaria `postcard`.
|
||||
pub fn serializar(&self) -> Result<Vec<u8>, &'static str> {
|
||||
postcard::to_allocvec(self).map_err(|_| "superbloque :: serializacion fallida")
|
||||
}
|
||||
|
||||
/// Reconstruye el superbloque desde el sector 0. Tolera el relleno a cero
|
||||
/// que completa el sector: solo consume el prefijo serializado.
|
||||
pub fn deserializar(bytes: &[u8]) -> Result<SuperBloque, &'static str> {
|
||||
postcard::take_from_bytes::<SuperBloque>(bytes)
|
||||
.map(|(sb, _)| sb)
|
||||
.map_err(|_| "superbloque :: deserializacion fallida")
|
||||
}
|
||||
}
|
||||
|
||||
impl Manifiesto {
|
||||
/// Serializa el manifiesto a su forma binaria `postcard` — la carga util
|
||||
/// del objeto del grafo que lo aloja.
|
||||
pub fn serializar(&self) -> Result<Vec<u8>, &'static str> {
|
||||
postcard::to_allocvec(self).map_err(|_| "manifiesto :: serializacion fallida")
|
||||
}
|
||||
|
||||
/// Reconstruye un manifiesto desde la carga util de su objeto. Rechaza un
|
||||
/// formato de version desconocida en lugar de malinterpretarlo.
|
||||
pub fn deserializar(bytes: &[u8]) -> Result<Manifiesto, &'static str> {
|
||||
let (manifiesto, _) = postcard::take_from_bytes::<Manifiesto>(bytes)
|
||||
.map_err(|_| "manifiesto :: deserializacion fallida")?;
|
||||
if manifiesto.version != VERSION_MANIFIESTO {
|
||||
return Err("manifiesto :: version de formato desconocida");
|
||||
}
|
||||
Ok(manifiesto)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// El hash y el trazado de un registro en el log
|
||||
// =============================================================================
|
||||
|
||||
/// La identidad de un objeto: el hash BLAKE3 de su forma serializada. Kernel y
|
||||
/// `boot` la calculan por aqui — una sola definicion del hash, jamas dos.
|
||||
pub fn hash(bytes: &[u8]) -> Hash {
|
||||
*blake3::hash(bytes).as_bytes()
|
||||
}
|
||||
|
||||
/// Numero de sectores que ocupa un registro cuyo payload mide `longitud`
|
||||
/// bytes. Cada registro es `[longitud: u32 LE][payload postcard][relleno 0]`.
|
||||
pub fn sectores_registro(longitud: usize) -> u64 {
|
||||
(4 + longitud).div_ceil(TAM_SECTOR) as u64
|
||||
}
|
||||
|
||||
/// Compone el registro en disco de un payload: `[longitud u32 LE][payload]
|
||||
/// [relleno a cero]`, alineado a un numero entero de sectores. Es el trazado
|
||||
/// exacto que el kernel lee al reconstruir su indice — lo escriben tanto
|
||||
/// `kernel::almacen` (al anexar un objeto) como `boot` (al sembrar la imagen).
|
||||
pub fn componer_registro(payload: &[u8]) -> Vec<u8> {
|
||||
let n = sectores_registro(payload.len()) as usize;
|
||||
let mut registro = vec![0u8; n * TAM_SECTOR];
|
||||
registro[0..4].copy_from_slice(&(payload.len() as u32).to_le_bytes());
|
||||
registro[4..4 + payload.len()].copy_from_slice(payload);
|
||||
registro
|
||||
}
|
||||
|
||||
/// Lee la cabecera de longitud de un registro (sus 4 primeros bytes). Devuelve
|
||||
/// `None` si la longitud es cero —fin del log— o supera [`MAX_OBJETO`]
|
||||
/// —corrupcion—. Gemela de [`componer_registro`].
|
||||
pub fn longitud_registro(cabecera: &[u8]) -> Option<usize> {
|
||||
if cabecera.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
let longitud =
|
||||
u32::from_le_bytes([cabecera[0], cabecera[1], cabecera[2], cabecera[3]]) as usize;
|
||||
if longitud == 0 || longitud > MAX_OBJETO {
|
||||
None
|
||||
} else {
|
||||
Some(longitud)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pruebas — el formato debe ser un espejo perfecto: lo escrito se relee igual
|
||||
// =============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod pruebas {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn objeto_ida_y_vuelta() {
|
||||
let objeto = Objeto {
|
||||
datos: vec![1, 2, 3, 4, 5],
|
||||
hijos: vec![[7u8; 32], [9u8; 32]],
|
||||
};
|
||||
let bytes = objeto.serializar().unwrap();
|
||||
assert_eq!(Objeto::deserializar(&bytes).unwrap(), objeto);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registro_alineado_a_sector() {
|
||||
let payload = vec![0xABu8; 600];
|
||||
let registro = componer_registro(&payload);
|
||||
// 4 + 600 = 604 bytes => dos sectores de 512.
|
||||
assert_eq!(registro.len(), 2 * TAM_SECTOR);
|
||||
assert_eq!(registro.len() % TAM_SECTOR, 0);
|
||||
assert_eq!(longitud_registro(®istro), Some(600));
|
||||
assert_eq!(®istro[4..604], &payload[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cabecera_a_cero_es_fin_del_log() {
|
||||
assert_eq!(longitud_registro(&[0, 0, 0, 0]), None);
|
||||
assert_eq!(longitud_registro(&[0xFF, 0xFF, 0xFF, 0xFF]), None);
|
||||
assert_eq!(longitud_registro(&[3, 0, 0, 0]), Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifiesto_rechaza_version_ajena() {
|
||||
let mut manifiesto = Manifiesto {
|
||||
version: 99,
|
||||
apps: Vec::new(),
|
||||
};
|
||||
let bytes = postcard::to_allocvec(&manifiesto).unwrap();
|
||||
assert!(Manifiesto::deserializar(&bytes).is_err());
|
||||
manifiesto.version = VERSION_MANIFIESTO;
|
||||
assert!(Manifiesto::deserializar(&manifiesto.serializar().unwrap()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superbloque_cabe_en_un_sector_y_vuelve_intacto() {
|
||||
let sb = SuperBloque {
|
||||
magia: MAGIA,
|
||||
version: VERSION_SUPERBLOQUE,
|
||||
cursor: 4096,
|
||||
raiz: Some([1u8; 32]),
|
||||
manifiesto: Some([2u8; 32]),
|
||||
};
|
||||
let bytes = sb.serializar().unwrap();
|
||||
assert!(bytes.len() <= TAM_SECTOR);
|
||||
assert_eq!(SuperBloque::deserializar(&bytes).unwrap(), sb);
|
||||
}
|
||||
}
|
||||
@@ -45,19 +45,14 @@ wasmi = { version = "1.0", default-features = false, features = ["hash-collectio
|
||||
# `virtio-drivers` bare-metal: el kernel implementa su `trait Hal` para el DMA.
|
||||
virtio-drivers = { version = "0.13", default-features = false, features = ["alloc"] }
|
||||
|
||||
# --- Fase 6.1c :: el grafo de objetos direccionado por contenido ---
|
||||
# `serde` da el rasgo de (de)serializacion; `postcard` lo materializa en un
|
||||
# formato binario compacto, pensado para sistemas empotrados — el que viaja al
|
||||
# disco. Ambos `no_std`, apoyados en `alloc`.
|
||||
serde = { version = "1", default-features = false, features = ["alloc", "derive"] }
|
||||
postcard = { version = "1", default-features = false, features = ["alloc"] }
|
||||
# `blake3`: la funcion hash que da identidad a cada objeto. Se fuerza la
|
||||
# implementacion ESCALAR pura (`pure` + los cuatro `no_*`): el target del kernel
|
||||
# corre sin SSE, y un camino SIMD activado por deteccion en tiempo de ejecucion
|
||||
# ejecutaria instrucciones que la CPU, sin `CR4.OSFXSR`, rechazaria con un #UD.
|
||||
blake3 = { version = "1", default-features = false, features = [
|
||||
"pure", "no_sse2", "no_sse41", "no_avx2", "no_avx512",
|
||||
] }
|
||||
# --- Fase 6.1c / 7b :: el grafo de objetos direccionado por contenido ---
|
||||
# El formato del grafo —tipos, (de)serializacion postcard, hash BLAKE3, trazado
|
||||
# de registros del log— vive en la crate `formato`, un nucleo `no_std`
|
||||
# COMPARTIDO con `boot` (que lo usa para sembrar la imagen de disco). El kernel
|
||||
# ya no declara `serde`/`postcard`/`blake3` por su cuenta: los hereda —con las
|
||||
# mismas features, BLAKE3 escalar puro incluido— a traves de `formato`. Una
|
||||
# sola verdad del formato de disco, imposible de divergir entre los dos lados.
|
||||
formato = { path = "../formato" }
|
||||
|
||||
# --- Fase 8 (preparación) :: el compositor ---
|
||||
# `mirada-layout` es el motor de teselado del compositor de brahman —
|
||||
|
||||
Reference in New Issue
Block a user