gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 815Rust 56Shell 37Markdown 291229 Definiciones

digest: blake3 pelado, no length-prefijado — el mismo archivo tenía dos hex

Lo destapó escribir la contraparte del ADR 0014 para churay: churay direcciona sus blobs
con blake3(bytes) pelado y hammer iba a sellar el digest con of_inputs(&[bytes]), los dos
bajo el prefijo `b3:`. El mismo archivo con dos hex distintos, y un CAS compartido que no
falla ruidosamente: cada lado busca un nombre distinto para los mismos bytes.

Con una sola entrada el length-prefijado no desambigua ninguna concatenación — sólo hace
que el nombre deje de ser verificable por un tercero con b3sum en la mano.

`ArtifactHash::of_bytes` YA EXISTÍA y ya era blake3 pelado: es la convención de of_file y
la del expected_hash de un .swm, así que of_inputs era además la pieza fuera de sitio
dentro del propio repo. of_inputs se queda para lo que fue escrito: hashear una LISTA.

Coste: una línea, porque ningún índice publicado lleva todavía el campo. Es el argumento
del ADR aplicado a sí mismo — decidir antes de que haya usuarios. La corrección queda en
el ADR, no reescrita en silencio.

Guardián: digest_es_blake3_pelado_y_no_length_prefijado, con vector fijo de b3sum y un
assert_ne contra of_inputs que nombra la consecuencia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CK6HpSoHcN9M4GBpqRSusR

8b2ff3cfd4747f0496f77f392a3e40dd5e49d714 · por Sergio · (hace 1 mes) · padre fbf402d51 · ver el árbol en este commit

Delta semántico · 2

crates/hammer-core/src/repo.rs

3 archivos · +53 −7

diff --git a/crates/hammer-core/src/hash.rs b/crates/hammer-core/src/hash.rs
index 03b7f37f..39cf3784 100644
--- a/crates/hammer-core/src/hash.rs
+++ b/crates/hammer-core/src/hash.rs
@@ -101,6 +101,13 @@ impl ArtifactHash {
     /// BLAKE3 crudo de un buffer en memoria — exactamente `blake3(bytes)`, la misma convención que
     /// [`of_file`](Self::of_file). Lo usa el checker de evidencia (H1b) para anclar el stdout de un
     /// check contra su `expected_output`: quien recompute `blake3` de la salida obtiene el mismo `b3:…`.
+    ///
+    /// **Ésta, y no [`of_inputs`](Self::of_inputs), es la que direcciona un blob por su contenido**
+    /// (ADR 0014): con una sola entrada el length-prefijado no desambigua nada y cuesta que el
+    /// nombre deje de ser verificable por un tercero con `b3sum` en la mano. Vale también fuera del
+    /// repo: churay (tawasuyu) direcciona sus blobs con esta misma función bajo el mismo prefijo
+    /// `b3:`, así que un CAS compartido sólo cierra si los dos lados usan ésta — con `of_inputs` el
+    /// mismo archivo tendría dos hex distintos y la discrepancia no fallaría ruidosamente.
     pub fn of_bytes(bytes: &[u8]) -> ArtifactHash {
         ArtifactHash(format!("b3:{}", blake3::hash(bytes).to_hex()))
     }
diff --git a/crates/hammer-core/src/repo.rs b/crates/hammer-core/src/repo.rs
index 1da8652c..bcf8e211 100644
--- a/crates/hammer-core/src/repo.rs
+++ b/crates/hammer-core/src/repo.rs
@@ -23,12 +23,13 @@ pub const INDEX_FILE: &str = "index.json";
 
 /// El `digest` de unos bytes de `.swm`, en el mismo formato `b3:…` que un [`crate::ArtifactHash`].
 ///
-/// Length-prefijado como todo lo demás: la función es la misma que sella artefactos, no una segunda
-/// convención de hash conviviendo con la primera.
+/// **BLAKE3 pelado** ([`ArtifactHash::of_bytes`](crate::ArtifactHash::of_bytes)), no
+/// `of_inputs(&[bytes])`: un blob se direcciona con el hash que cualquiera obtiene de esos bytes —
+/// el de `b3sum` — o el nombre deja de ser verificable por un tercero. Es además el mismo que usa
+/// churay para sus blobs, y con `of_inputs` el mismo archivo tendría dos hex distintos bajo el
+/// mismo prefijo en los dos proyectos.
 pub fn digest_swm(bytes: &[u8]) -> String {
-    crate::ArtifactHash::of_inputs(&[bytes])
-        .as_str()
-        .to_string()
+    crate::ArtifactHash::of_bytes(bytes).as_str().to_string()
 }
 
 /// Comprueba que `bytes` casan con el `digest` que el índice **firmado** declara para esa entrada.
@@ -269,6 +270,29 @@ mod tests {
         );
     }
 
+    /// Guardián de interoperabilidad: el `digest` tiene que ser el BLAKE3 que obtiene CUALQUIERA
+    /// de esos bytes (`b3sum`), no `of_inputs(&[bytes])`. Es la §5 del SDD de churay: el mismo
+    /// archivo con dos hex distintos bajo el mismo prefijo `b3:` no falla ruidosamente — los dos
+    /// lados buscan nombres distintos para los mismos bytes.
+    #[test]
+    fn digest_es_blake3_pelado_y_no_length_prefijado() {
+        let bytes = b"contenido del .swm";
+        assert_eq!(
+            digest_swm(bytes),
+            crate::ArtifactHash::of_bytes(bytes).as_str()
+        );
+        assert_ne!(
+            digest_swm(bytes),
+            crate::ArtifactHash::of_inputs(&[bytes.as_slice()]).as_str(),
+            "volver a of_inputs rompe el CAS compartido con churay, y en silencio"
+        );
+        // Vector fijo: si `of_bytes` cambiara de convención, esto lo caza igual.
+        assert_eq!(
+            digest_swm(b"abc"),
+            "b3:6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85"
+        );
+    }
+
     #[test]
     fn digest_ausente_no_cambia_la_serializacion() {
         // `skip_serializing_if` ⇒ un índice ya firmado antes de este campo serializa IDÉNTICO y su
diff --git a/docs/adr/0014-distribucion-multiorigen.md b/docs/adr/0014-distribucion-multiorigen.md
index f6c49281..8526acd4 100644
--- a/docs/adr/0014-distribucion-multiorigen.md
+++ b/docs/adr/0014-distribucion-multiorigen.md
@@ -100,10 +100,25 @@ alcanza: `expected_hash` es **opcional**, y el `.swm` se lee mucho antes de lleg
 colisiones de ficheros de `install` ya decide con su contenido). El comentario de `download.rs`
 decía que el `patch_url` «se cubre indirectamente»; *indirectamente* no es una cadena de confianza.
 
-Se cierra con `PackageEntry::digest` (BLAKE3, `b3:…`, la misma función que sella artefactos — no una
-segunda convención de hash conviviendo con la primera), sellado al publicar y verificado antes de
+Se cierra con `PackageEntry::digest` (BLAKE3, `b3:…`), sellado al publicar y verificado antes de
 escribir un byte. La cadena queda: **clave raíz → firma del índice → `digest` → bytes**.
 
+> **CORRECCIÓN (mismo día).** Este párrafo decía que el `digest` usaba «la misma función que sella
+> artefactos», o sea `of_inputs(&[bytes])` — length-prefijado. **Estaba mal, y la razón importa.**
+> Con una sola entrada el length-prefijado no desambigua ninguna concatenación: sólo hace que
+> `b3:<hex>` **deje de ser el hash que obtiene un tercero** con `b3sum` sobre el mismo archivo.
+>
+> Lo destapó escribir la contraparte de este ADR para churay (`02_ruway/churay/SDD-DISTRIBUCION.md`
+> §5 en tawasuyu): churay direcciona sus blobs con `blake3(bytes)` pelado y hammer iba a hacerlo
+> length-prefijado, **bajo el mismo prefijo `b3:`** ⇒ el mismo archivo con dos hex distintos, y un
+> CAS compartido que no falla ruidosamente sino que busca nombres distintos para los mismos bytes.
+>
+> Ahora usa `ArtifactHash::of_bytes` — que **ya existía en hammer** y ya era blake3 pelado; es la
+> convención de `of_file` y la del `expected_hash` de un `.swm`, así que `of_inputs` era además la
+> pieza fuera de sitio *dentro* del propio repo. Coste del cambio: una línea, porque ningún índice
+> publicado llevaba todavía el campo. Es el argumento de este ADR aplicado a sí mismo — decidir
+> antes de que haya usuarios. Hay guardián: `digest_es_blake3_pelado_y_no_length_prefijado`.
+
 El campo es `Option` con `skip_serializing_if`, así que **un índice firmado antes de este cambio
 serializa idéntico y su firma sigue siendo válida** (hay test). Un índice sin digests no bloquea,
 pero **dice cuántos paquetes no verificó**: un índice viejo servido desde un espejo ajeno no es