tawasuyu

tawasuyu/brahman

Rust 492TOML 194Markdown 53JSON 176939 Definiciones

fix(nakui-core): schema_bundle_hash usa bytes reales del schema, no del bundle

Iter 17. Regresión surfaceada por verify_log_rejects_seed_after_schema_kcl_changes.

Bug: compute_schema_bundle_hash operaba sobre los bytes del bundle
compilado, que es `(import "/abs/path") & ...`. Esos bytes no cambian
cuando se edita el archivo apuntado; sólo cambian si se mueve el
módulo o se agregan/quitan schemas. El hash quedaba pegado y un seed
firmado con schema vN se verificaba ok contra schema vN+1.

Fix: nueva fn read_schema_files_concat que lee cada schema declarado
y los concatena con framing `\0NCL:<name>\0`. Esos bytes alimentan
los dos hashers (schema_bundle_hash y morphism_schema_hash). El bundle
compilado sigue siendo imports-style (Nickel necesita los paths para
resolver), sólo la fuente del hash cambia.

Impacto: logs versados con el binario anterior fallan SchemaMismatch
al verificarse — comportamiento correcto (re-seed).

Test renombrado: verify_log_rejects_seed_after_schema_kcl_changes →
_after_schema_changes (residuo de la migración KCL→Nickel).

10/10 schema_versioning verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

7fb2ad3b1ebba21ac0dd997f8e282f67c847888b · por Sergio · (hace 5 meses) · padre be3a0e78f · ver el árbol en este commit

Delta semántico · 4

crates/modules/nakui/core/src/executor.rs

crates/modules/nakui/core/tests/schema_versioning.rs

3 archivos · +60 −2

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99b166e..e399af2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,37 @@ ratio/diff ver `git show <sha>`.
 
 ## 2026-05-10
 
+### fix(nakui-core): schema_bundle_hash debe reflejar el contenido real del schema
+Iter 17. Regresión surfaceada por el workspace test
+`verify_log_rejects_seed_after_schema_kcl_changes` (rebautizado a
+`verify_log_rejects_seed_after_schema_changes`).
+
+**Bug**: `compute_schema_bundle_hash` operaba sobre los bytes del
+bundle compilado, que `build_schema_bundle` arma como
+`(import "/abs/path/schema.ncl") & ...`. Los bytes del bundle nunca
+cambian cuando se edita el archivo apuntado — sólo cambian si se
+agregan/quitan schemas o se mueve el módulo de path. Resultado: el
+hash quedaba pegado y los seeds firmados bajo schema vN se aceptaban
+silenciosamente como válidos bajo schema vN+1, aunque las invariantes
+hubieran cambiado.
+
+**Fix**: nueva fn `read_schema_files_concat(module_dir, schemas)`
+que lee los bytes de cada schema declarado y los concatena con
+framing `\0NCL:<name>\0` (separador no ambiguo + nombre relativo, no
+absoluto, para que el hash sea estable entre máquinas). Esos bytes
+alimentan `compute_schema_bundle_hash` y `compute_morphism_schema_hash`
+en lugar de los bytes del bundle. El bundle compilado sigue siendo
+imports-style (necesario para que Nickel resuelva paths relativos);
+sólo la fuente del hash cambió.
+
+**Impacto en logs existentes**: como cualquier cambio al insumo del
+hash, los seeds y morphisms versados bajo el bundle hash anterior
+fallarán `SchemaMismatch` al verificarse contra un binario nuevo. No
+hay migración — esto es exactamente el comportamiento que el hash
+busca: re-seed.
+
+Tests: 10/10 en `schema_versioning` (era 9/10 con 1 FAILED).
+
 ### feat(yahweh-widget-app-header): promover el header standard de explorers
 Iter 16. Patrón con 4 consumers idénticos: `nakui-explorer`,
 `nouser-explorer`, `minga-explorer`, `brahman-broker-explorer`
diff --git a/crates/modules/nakui/core/src/executor.rs b/crates/modules/nakui/core/src/executor.rs
index 1707832..23ea7d9 100644
--- a/crates/modules/nakui/core/src/executor.rs
+++ b/crates/modules/nakui/core/src/executor.rs
@@ -136,7 +136,12 @@ impl Executor {
         let graph = ManifestGraph::build(&manifest)?;
         let schema_path = build_schema_bundle(&module_dir, &manifest.effective_schemas())?;
 
-        let schema_bundle_bytes = std::fs::read(&schema_path)?;
+        // Hash insumos = bytes reales de cada schema file, NO el bundle
+        // (que sólo contiene `import "/abs/path"` y no cambia cuando el
+        // archivo apuntado se mueve). Sin esto, el bundle hash quedaba
+        // pegado y la versión del seed nunca detectaba ediciones de
+        // schema. Ver `verify_log_rejects_seed_after_schema_changes`.
+        let schema_bundle_bytes = read_schema_files_concat(&module_dir, &manifest.effective_schemas())?;
         let schema_bundle_hash = compute_schema_bundle_hash(&schema_bundle_bytes);
         let mut schema_hashes = HashMap::with_capacity(manifest.morphisms.len());
         for spec in &manifest.morphisms {
@@ -519,6 +524,28 @@ pub fn normalize_rhai_source(src: &str) -> String {
 /// El path en el `import "..."` queda absoluto (resuelto desde
 /// `module_dir`) para que el evaluator lo encuentre desde el
 /// tempdir.
+/// Concatena los bytes de cada schema file declarado, en orden y con
+/// framing `\0NCL:<name>\0`, para alimentar el bundle hash con el
+/// contenido real de los schemas. El bundle compilado por
+/// `build_schema_bundle` sólo contiene imports de paths absolutos —
+/// inestables entre runs y, peor, invariantes a ediciones del archivo
+/// apuntado. Esta función entrega bytes que sí mueven el hash cuando
+/// cambia el schema en disco.
+fn read_schema_files_concat(
+    module_dir: &std::path::Path,
+    schemas: &[String],
+) -> std::io::Result<Vec<u8>> {
+    let mut out = Vec::new();
+    for s in schemas {
+        out.extend_from_slice(b"\0NCL:");
+        out.extend_from_slice(s.as_bytes());
+        out.push(0);
+        let bytes = std::fs::read(module_dir.join(s))?;
+        out.extend_from_slice(&bytes);
+    }
+    Ok(out)
+}
+
 fn build_schema_bundle(
     module_dir: &std::path::Path,
     schemas: &[String],
diff --git a/crates/modules/nakui/core/tests/schema_versioning.rs b/crates/modules/nakui/core/tests/schema_versioning.rs
index d8f478b..abf15a1 100644
--- a/crates/modules/nakui/core/tests/schema_versioning.rs
+++ b/crates/modules/nakui/core/tests/schema_versioning.rs
@@ -335,7 +335,7 @@ fn seed_and_log_writes_bundle_hash_into_seed_entries() {
 }
 
 #[test]
-fn verify_log_rejects_seed_after_schema_kcl_changes() {
+fn verify_log_rejects_seed_after_schema_changes() {
     let temp = TempModule::from(&treasury_module());
     let log_path = fresh_log_path();
     let id = Uuid::new_v4();