Barrido de TEXTO puro: 1427 líneas de comentario TOML. Cero ArtifactHash
movidos, y eso está MEDIDO, no deducido: hasheé las 741 antes y después y los
ficheros de hashes son idénticos byte a byte (723 con hash real + 18 que ya no
hasheaban de antes).
El guardián valió la pena: el primer barrido, filtrando por 'la línea empieza
con #', movió el hash de helix, lsof y steam-runtime-sniper. La causa es que una
fase se escribe como compile = <triple> ... <triple> y sus comentarios de SHELL
también empiezan con #, pero viven dentro del VALOR — y las fases sí entran en
hash_inputs. El barrido ahora calcula los rangos de las cadenas multilínea de
TOML y no entra ahí.
Quedan intactos a propósito: .hammer-zig-cc y .hammer-cargo-vendor (literales
dentro de fases), hammerd, hammer-recover y toda ruta que empiece por /
Tres arreglos que son el mismo: un campo que nadie lee es un campo que miente.
1. yupana._deps() leía SÓLO `deps.build`. Como yupana es la base de todas las
herramientas de grafo, una dep de EJECUCIÓN no existía para ninguna: ni el
vigía de sonames, ni la membresía de perfiles, ni el rootfs hidratado.
Medido: firefox declaraba `runtime = ["gcc-libs"]` y el cierre de las cuatro
imágenes seguía sin libstdc++.so.6, así que el navegador no arrancaba y el
vigía lo seguía reportando como hueco DESPUÉS de haberlo arreglado.
`deps.runtime` está en el esquema de hammer desde siempre. La unión es la
definición de cierre: para CORRER hacen falta las dos.
2. build-state.py, lo mismo y por lo mismo.
3. El vigía entra en el LATIDO y deja docs/state/sonames.txt. Existía desde
antes y contesta la pregunta que el grafo no contesta —no «¿está sellado?»
sino «¿arranca?»— pero NADIE LO CORRÍA: no estaba en cosecha-cron y no dejaba
fichero de estado. Por eso libstdc++.so.6, que rompía el navegador en los
CUATRO perfiles, estuvo en su salida sin que nadie lo leyera, y se
redescubrió arrancando atuq a mano. Un vigía que hay que acordarse de invocar
no se distingue de no tenerlo.
Y se BORRA scripts/audit-needed.sh, que escribí ayer sin ver que vigia-sonames.py
ya hacía exactamente esto, con la misma frase en la cabecera. Dos herramientas
que miden lo mismo divergen y la que nadie mira es la que miente; la que se
queda es la que ya existía, que además reporta POR PERFIL y encontró más cosas.
python3 declara sus cuatro deps de ejecución (readline/sqlite/lzma/bz2): son
módulos de la stdlib que se cargan por dlopen, así que no rompen el arranque
sino un `import` — un fallo que aparece lejos y no menciona a python.
Resultado, con todo aplicado: 29 huecos -> 6, y los que quedan son otra clase.
`libperl.so` es empaquetado de la receta perl; `libc.so.6` lo pide el `go`
prebuilt y es un soname de GLIBC en una distro musl, que es un síntoma distinto.
Los dos quedan anotados en docs/state/sonames.txt, que ahora se regenera solo.
CPython abortaba `_ssl` con «OPENSSL_THREADS is not defined, Python requires
thread-safe OpenSSL», y la receta lo daba por imposible sin rehacer openssl.
CAUSA RAÍZ, probada: no es openssl, es `link = "static"`.
`hammer-build/src/lib.rs:285` inyecta `LDFLAGS=-static`, y el Configure de
openssl (línea 1514) hace:
if (grep { $_ =~ /(?:^|\s)-static(?:\s|$)/ } @{$config{LDFLAGS}}) {
disable('static', 'pic', 'threads');
}
⇒ el propio `-static` apaga los threads en silencio. Añadir `threads` explícito
NO sirve (el disable() va después y gana): el configuration.h salía byte a byte
idéntico. Dentro del sandbox `configdata.pm` decía `thread_scheme => pthreads`
pero `THREADS_DEFINE=0`; fuera del sandbox, los mismos flags —y hasta con el
perl del corpus— SÍ definían el macro. La diferencia era el entorno.
`openssl-threads`: misma receta con `link = "dynamic"` (mantiene `no-shared`,
sigue emitiendo .a). Verificado: OPENSSL_THREADS definido y 36 símbolos
pthread_ reales en libcrypto.a.
VARIANTE y no tocar `recipes/openssl.toml`: de la canónica cuelgan los CUATRO
kernels (linux, linux-generic, linux-metal, linux-metal-dual) y la reproducción
bit a bit del 6.16.12 es un resultado cerrado; moverle el hash lo invalidaría,
más 125 sellados.
Además `ssl` entra en la asserción del install. Sin ese guardián se volvería a
sellar un intérprete mudo — que es justo como esto estuvo escondido meses.
Verificado: `checking for stdlib extension module _ssl... yes`,
_ssl.cpython-312-x86_64-linux-musl.so instalada, y el propio install imprime
`modulos opcionales: OK` importando ssl.
⚠ Mueve el hash de python3: 251 dependientes directos, ~369 sellados en los
cinco grafos (161 en KDE). Es el coste aceptado de la decisión.
Decision del usuario. CPython construye sus modulos opcionales como .so
COMPARTIDOS y las .a del corpus NO son PIC («relocation R_X86_64_PC32 against
symbol 'stdscr'»), asi que declararlas en [deps] NO funciona: se probo una
por una. Las libs del rootfs son compartidas y si sirven.
Añadidos: libffi-dev ncurses-dev readline-dev sqlite-dev bzip2-dev xz-dev
expat-dev. Resultado VERIFICADO: el guardian de python3 importa los OCHO
modulos (_ctypes _curses readline sqlite3 bz2 lzma zlib pyexpat) y pasa. De
paso `libffi` sale de [deps]: ya lo aporta el lab.
openssl-dev NO va, deliberado: se de-Alpinizo porque el host-tool del kernel
enlaza el openssl del corpus desde el overlay; devolverlo podria cambiar el
artefacto del kernel. python3 sigue sin `ssl`.
Y SE CIERRA EL AGUJERO QUE ESTO ABRIA. Esas libs del rootfs ahora entran en
el CONTENIDO de un artefacto, asi que su version es parte de su identidad ⇒
van a TOOLCHAIN_PREFIXES. Sin eso habriamos reabierto en pequeño el mismo
fallo que 58d3161 cerro: instalar los -dev subio ncurses 6.5→6.6 y readline
8.3.1→8.3.3 —sin tocar gcc/rust/musl, lo confirmo el lock— y ese salto habria
cambiado el python3 producido SIN mover su direccion.
Coste: la huella del lab cambia ⇒ el corpus se re-hashea otra vez. Se hace
AHORA a proposito, con 194 artefactos, no con el corpus a medio construir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Estado VERIFICADO: con libffi el build sella y el guardian de la fase install
imprime «modulos opcionales: OK» ⇒ _ctypes y zlib importan de verdad.
El guardian es la pieza que mas vale de este commit: `configure` de CPython
OMITE en silencio cada modulo opcional cuyos headers no encuentra, `make`
termina 0 y el artefacto se sella mutilado. El error aparece dias despues en
OTRA receta (gjs, gdm) como ModuleNotFoundError y no se parece a su causa.
Importarlos en install lo convierte en un fallo en la receta correcta.
Lo que NO se pudo añadir, probado uno por uno y documentado en la receta:
openssl → el del corpus es libcrypto ESTATICA sin threads; CPython aborta
con «OPENSSL_THREADS is not defined». Sin modulo `ssl`.
ncurses → `.a` no-PIC: _curses_panel.so no enlaza
(«relocation R_X86_64_PC32 ... against symbol 'stdscr'»).
sqlite/bzip2/xz/readline/expat → mismo muro no-PIC (_lzma lo destapo).
CPython construye sus modulos opcionales como .so COMPARTIDOS, y las .a del
corpus no son PIC. El python3 viejo tenia _ctypes Y _curses porque se
construyo contra las libs COMPARTIDAS del rootfs de entonces; el lab anclado
trae los runtime pero no los headers.
⇒ _curses sigue roto y con el la familia GNOME. Arreglarlo pide DECIDIR:
meter los -dev en la imagen del lab (re-hashea el corpus otra vez, barato
AHORA que van 48 de 1186) o hacer recetas PIC de esas libs (frente nuevo).
Queda para el usuario.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Destapado reconstruyendo el corpus: gjs y el build de mozjs morian con
`ModuleNotFoundError: No module named '_ctypes'`. La causa no estaba ahi: el
artefacto de python3 se sella SIN ese modulo y arranca perfectamente; el
fallo aparece mucho despues, en otra receta. 310 recetas dependen de python3.
El rootfs del lab trae libffi (runtime) pero NO sus headers — no hay
libffi-dev en el lock. El configure de CPython no encuentra ffi y omite
_ctypes sin fallar. recipes/libffi.toml si instala headers y .pc, y pkgconf
—ya declarado— los ve en la capa overlay.
Por que aparece AHORA y no antes: el artefacto viejo de python3 tenia _ctypes
porque el rootfs de entonces traia los headers, y el cache-hit lo mantuvo
congelado hasta que el corpus se re-hasheo. Mismo patron que
make/--disable-load: reconstruir destapa lo que la cache sostenia.
Se arregla ahora y no mas tarde a proposito: mover el hash de python3 mueve
el de sus 310 dependientes, y con solo ~48 recetas selladas eso es barato.
Dentro de dos dias no lo habria sido.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La deuda legal bloqueante del SDD 19 §2.1. Medido hoy: **0 de 1141 recetas** declaraban
licencia, no «5 de 771» como decía el informe anterior. Los dos números estaban mal: los
«5» eran falsos positivos de `grep license` (el paquete `addlicense`, el paquete
`cargo-bundle-licenses`, una línea `install .../share/licenses/` y un comentario), y las
recetas son 1141. Contar con `grep -l <palabra>` sobre TOML cuenta comentarios y nombres,
no campos; `scripts/licencias.sh` cuenta el campo de verdad (clave en la raíz, antes del
primer `[table]`).
LO QUE HACE LA DEUDA PAGABLE: `Recipe::hash_inputs` es una LISTA BLANCA — sólo entran
source, compiler, target, link, patches, flags, phases y deps. `license` no entra, igual
que `evidence` y `slots`. Por eso se puede poblar en las recetas YA SELLADAS sin mover un
solo ArtifactHash. Verificado, no supuesto: en 40 recetas modificadas se comparó el hash
con y sin la línea — 40 idénticos, 0 cambiados. Si el campo entrara al hash, declarar la
licencia costaría reconstruir el corpus entero y no se haría nunca. Clavado con el test
`licencia_round_trip_y_no_afecta_el_hash`.
TRAMPA DE TOML: una clave suelta después de un `[table]` pertenece a esa tabla. Puesta al
final del fichero, `license` acaba dentro de `[deps]` y se pierde EN SILENCIO, porque serde
ignora los campos que no conoce — no hay error, simplemente no está. Va arriba, junto a
`name` y `version`; el sembrador la inserta tras `version`.
NO SE ADIVINA. Declarar mal una licencia es peor que dejarla vacía: convierte un hueco
visible en una afirmación falsa. Sólo se puebla desde una tabla curada entrada por entrada
(`docs/licencias-conocidas.tsv`); lo que no tiene evidencia queda vacío y se CUENTA.
Concretamente se descartó el atajo «k* = KDE ⇒ LGPL»: en este catálogo `kail`, `kind`,
`ko`, `kopia`, `krew`, `kustomize`, `kyverno`, `katana`, `kibi`, `kmon` y toda la familia
`kube*` son herramientas Go sin relación con KDE. El nombre no es evidencia.
Quedan 913, casi todas CLIs Go/Rust importados en masa — y ésas sí son automatizables con
evidencia real: Cargo.toml trae el campo `license` y los módulos Go traen su LICENSE en el
árbol. El cierre estructural es capturarlo en la fase de fetch, que ya descarga y extrae
cada tarball, y inyectar el texto en `hammer pack` (aguas abajo del ArtifactHash) en vez de
en la fase install (que sí re-hashearía).
De paso, respaldo-storagebox.sh reordenado por valor irreemplazable y con zstd: medido en
la oficina, el uplink da 8 Mbps iguales por cable y por wifi ⇒ 128 G no caben en una
sentada, así que sube primero el cerebro (estado + repo) y `--partial-dir` hace que cortar
a mitad de un artefacto no tire lo ya subido.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DOS ERRORES MÍOS, uno de dirección y otro técnico, los dos en la cosecha automática:
1. DIRECCIÓN: declaró `busybox` como dep en 29 recetas — cuando la Etapa C lo está
ELIMINANDO (USERLAND_COMPONENTS=[uutils,findutils,…], ya cerrada; joyas-reusables
§5 lo confirma: "uutils… Ubuntu 25.10 los envía como default"). Estaba cimentando
la deuda que el roadmap borra.
2. TÉCNICO: busybox YA está en el runtime base de harkaq (`ro /bin/busybox`, `ro
/bin/sh` — el sandbox corre `sh -c` y /bin/sh→/bin/busybox). Es CONTRATO, no dep.
Declararlo apila el busybox de hammer sobre el de Alpine y ROMPE el build:
binutils daba "cannot run C compiled programs" en las DOS máquinas. Verificado:
sin busybox declarado, binutils construye (b3:f4507dcd…). Y la cadena se explica:
binutils roto ⇒ zlib (que lo declara) tampoco construía.
Auditoría de la cosecha (diff real, no la línea completa del +): añadió sólo 5 deps
distintas — make ×73, busybox ×29, perl ×8, pkgconf ×5, binutils ×1. Sólo busybox
estaba mal; las otras 4 son deps reales medidas. busybox revertido de 30 recetas
(queda sólo en busybox.toml, pre-existente).
Es el mismo error que ya me habían señalado con otro disfraz: MEDIR BIEN Y ACCIONAR
MAL. harkaq midió correcto (el build toca /bin/busybox: es el shell); la acción
correcta no era declararlo sino reconocerlo como contrato.
+ COORDINACIÓN del §3 con Fable 5 (mismo diseño, tareas repartidas):
- Su lección casper queda CONFIRMADA y REFORZADA: la clausura de build no sólo le
FALTAN las clases del mundo (offline) — también le SOBRA casi todo (headers, gcc).
Medido: htop (estático, 0 NEEDED) no toca NADA al correr ⇒ política = su binario.
- Su "la clase viaja como campo de la ConcesionCapacidad, sin formato nuevo" se
cumple LITERALMENTE: lo firmado es format::Permisos = u32 bitmask en 36 bytes
canónicos (Ring 0) ⇒ las clases SON los bits. La cripto no se toca.
- Diseño unificado: frontera (clases, u32, declaradas) + detalle (paths, Landlock,
medidos). D3 rige en ambos.
- Reparto: clases→Fable 5; medición/harness→Opus. CONTACTO: runtime-policy.sh ahora
emite la CLASE detectada (/etc/resolv.conf→dns, /etc/ssl/certs→tls-certs, …), no
sólo el path: la medición alimenta la tabla, la tabla decide el bit.
- Consumidor esperando: plan-jaula-juegos F1 (Steam que no puede leer ~/.ssh).
+ juez.sh (§10): nombre único por corrida (con uno fijo pega en caché ⇒ falso
"sin evidencia"). Fue el juez quien destapó todo esto en su primera corrida real.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cosecha desatendida de la campaña harkaq. Cada dep de acá la denunció el audit de
Landlock en un build real: el kernel vio al build usarla sin declararla, y el
store del hub confirma qué artefacto la provee (deuda DECLARABLE, §4.5).
Recetas:gettext-tiny giflib git gnupg gperf gzip htop iproute2 jq kbd libassuan libcap libevent libffi libgcrypt libgpg-error libksba libnl libpng libsass libsodium libssh2 libudev-zero libusb libwebp libxml2 libyaml linux-generic linux-headers linux-metal linux-pam linux lz4 mandoc mtools musl nano npth openssh openssl parted pciutils pcre2 pigz procps-ng python3 readline rsync samurai ca-certificates curl doas dosfstools e2fsprogs fontconfig freetype
No se tocó ninguna receta con deuda IRREDUCIBLE: declarar algo que el store no
provee rompería el build en vez de arreglarlo. Esas van a tandas/needs-review-harkaq.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mata gcc para 5 de las 7 recetas que lo forzaban, vía una escotilla nueva:
- hammer-core/hammer-build: campo `[build].zig_version` por receta. Cuando se
fija, el lab resuelve ese zig (hermano del por defecto, `zig-x86_64-linux-<v>`)
en vez del global, y entra al hash SÓLO si está presente (baseline 9adefb82
intacto). `effective_zig_dir` lo aplica en ensure_layout + Sandbox.
- Causa: BISECT con oráculo flex (reproducido sólo vía lab: musl DINÁMICO) — el
miscompile es una REGRESIÓN de zig 0.14; 0.13.0 compila limpio, 0.14/0.15/0.16
fallan. Es C/musl-dinámico, NO afecta C++.
- Flip a zig_version="0.13.0" (quitando CC=gcc): flex, openssl, elfutils,
binutils, python3. Verificados: `as` 2.45.1 corre, python3 3.12.10 corre
(deepfreeze OK), libcrypto/libelf sellan. Todas son tools (no inputs del 4/4).
cmake queda en gcc: su segfault es C++ (libc++/musl), bug distinto que 0.13 NO
arregla (ni con -static). El kernel queda pendiente de verificar.
Tests: hammer-core/hammer-build verdes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El rebuild de python3 con zig cc pasó el date-time pero el intérprete
`_bootstrap_python` (zig cc) SEGFAULTEÓ en el paso de "freeze modules" —
mismo fallo que cmake: zig 0.16 miscompila binarios musl grandes (un
hello-world dinámico zig sí corre). Fix idéntico: CC=gcc/CXX=g++ en las
fases (gcc nativo del toolchain, como compila Alpine). Con gcc además
-Wdate-time deja de molestar (gcc honra SOURCE_DATE_EPOCH).
NOTA: rebuild gcc EN CURSO (fase compile, ya pasó configure) al commitear;
aún no sellado. zlib (f3ddad6) validado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primer intento de ambas falló; correcciones tras diagnóstico con probes aislados:
- cmake: el cmake mínimo que `./bootstrap` compila con `zig c++` SEGFAULTEA al
correr ("Problem while running initial CMake") — interacción libc++/musl de
zig. Fix: forzar CC=gcc/CXX=g++ en las fases (Alpine compila cmake con gcc;
el toolchain ya trae gcc/g++, nativos y funcionales en el sandbox).
- python3: Modules/getbuildinfo.c usa __DATE__/__TIME__ y la build habilita
-Wdate-time; zig (clang) lo promueve a ERROR. Probé que SOURCE_DATE_EPOCH=1
(que el sandbox ya exporta) fija el VALOR a 1970-01-01 (determinista) pero
clang NO silencia -Wdate-time vía SDE (eso es sólo-GCC). Fix:
CFLAGS="-Wno-error=date-time" — degrada a warning, valor sigue determinista.
NOTA: ambos rebuilds estaban EN CURSO al commitear (aún no sellados en store);
si fallan otra vez, commit de seguimiento. zlib (f3ddad6) sí validado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dependencias de build que rustc usa para construir su LLVM bundled (mrustc
README: cmake ≥3.4.3 + python3). De-Alpinizan el camino de bootstrap de rust;
hoy satisfechas por apk de arranque.
- cmake 3.31.6: build vía ./bootstrap (zig cc/c++), enlace dinámico, OpenSSL OFF.
- python3 3.12.10: ./configure autotools (zig cc), deps.build=[zlib],
--without-ensurepip --disable-test-modules, enlace dinámico.
NOTA: ambos están EN VALIDACIÓN — sus builds estaban en fase `configure` (sin
errores, zig cc aceptado) al commitear, todavía NO sellados en store. Si la
compilación/instalación falla, requerirán ajuste en un commit de seguimiento.
zlib (f3ddad6) sí está validado y sellado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>