hworker-4 estuvo 5 DÍAS idle (carga 0.00, sin crontab, sin loops, 37G de KDE
sin cosechar) quemando dinero. El usuario había pedido esto explícitamente
—"los vps se autoapagan cuando se detectan idle por un tiempo"— y de sus 3
puntos (volumen / auto-apagado / cosecha) implementé el 1 y el 3. Éste faltaba.
EL FALLO ERA ESTRUCTURAL, no un olvido: farm-down.sh existe pero es MANUAL. El
modelo "efímero" dependía de que el agente se acordara de llamarlo — y si su
contexto se corta, o la sesión muere, el server queda vivo para siempre. Un
invariante que necesita que alguien recuerde NO es un invariante. Por eso el
switch vive EN EL WORKER: para apagarse no necesita ni al hub ni a mí.
BORRA, no apaga: en Hetzner un server apagado SIGUE COBRANDO (disco + IP). Un
poweroff daría sensación de ahorro y seguiría facturando. Precio: el token vive
en el worker (/etc/hammer-deadman.env 0600). Riesgo real y consciente; se acepta
porque la alternativa MEDIDA fue peor: 5 días de VPS idle.
Cuenta por INACTIVIDAD CONTINUA, no por antigüedad: cualquier señal de trabajo
(hammer build, worker-loop, heartbeat <30min) resetea los ticks. Guarda
/var/lib/hammer-no-borrar aborta el borrado si hay cosecha pendiente.
systemd timer y NO cron, por la lección medida: un cron "validado a mano" nunca
disparó porque el PID 1 de aquella máquina (arje-zero) no tenía crond. Validar
la LÍNEA no es validar que un demonio la ejecute. y el
farm-up comprueban que el timer quedó ACTIVO — evidencia, no fe.
Cableado en farm-up: todo worker NACE con el switch puesto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda de las 3 con gcc oculto en las fases (tras bzip2). La receta declaraba
compiler=zig-cc y su compile pasaba CC=gcc ⇒ el gcc de Alpine hacía el trabajo.
El frente contaba compiler="gcc" y no miraba las fases, así que pigz nunca
figuró como deuda. Queda cargo-edit de esa terna.
CC="$CC" usa el zig cc que el sandbox exporta por default (lib.rs:269,
Compiler::ZigCc no pisa CC).
Verificado: 0 NEEDED, corre en el host (pigz 2.8), comprime/descomprime,
bit-repro b3:f5614e5b6b0a ×2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
367 ficheros, lista intacta vs el sellado viejo. b3:2a937a913b52
LDFLAGS="-all-static -no-pie" inyectado en las líneas make de compile e install.
samurai revertida: con -static su binario (samu) SIGUE dinámico ⇒ el control la
echó atrás. libcap revertida: mi regex rompió el TOML (parse error línea 37);
su make ya pasa CC/BUILD_CC/AR explícitos y necesita mano, no regex.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El audit tomaba 'ls -d store/*-<n> | head -1' = orden ALFABÉTICO POR HASH, no el
artefacto vigente. El store guarda TODOS los sellados de una receta (expat tenía
5, de junio a hoy), así que auditaba uno de hace tres semanas. Mismo bug de
clase que comparar libpng.a con libpng16.a por find|head -1.
Con 'ls -dt' (más reciente): 28 → 11. Tres de las supuestas mentirosas
(cargo-audit, git-absorb, yazi) nunca lo fueron: su artefacto reciente ya era
estático y el audit leía el viejo.
Lo que NO cambia: el hallazgo central es real y verificado a mano. libtool
ignora el -static del lab, el curl sellado NO arrancaba en el host, y de las 14
que arreglé cada una se verificó contra sus artefactos previos (expat: los 4
viejos con NEEDED=1, el nuevo con 0). Estaban rotas de verdad.
HONESTIDAD escrita en el script: 'más reciente' ≠ 'vigente'. Lo vigente sería el
artefacto cuyo hash corresponde a la receta de HOY, y hammer no lo expone sin
construir (no hay hammer hash / dry-run). Por eso el audit va DESPUÉS del
rebuild, nunca antes.
Quedan 11: file, helix, libcap, libgcrypt, pcre2, pkgconf, samurai, tuc,
util-linux, xz + naabu (nuevo: Go con libc.so.6 de GLIBC, otro caso).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fases multilínea: LDFLAGS inyectado en las líneas make de compile e install.
Control de lista de ficheros intacta: libwebp 33, kbd 689, sudo 32, lsof 4.
libwebp b3:138b70a6962f · kbd b3:248de1120197 · sudo b3:6fdbe83990ef ·
lsof b3:d30ddb01b86e
mandoc revertida: no usa libtool ("Unknown Clang option: '-all-static'") y con
-static pierde binarios (apropos, demandoc) ⇒ el control la echó atrás. Necesita
otro enfoque, como xz y pcre2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parted es el caso didáctico: YA tenía -all-static en compile — era el precedente
que citaban jq/shadow/procps-ng — y aun así salía dinámico, porque le faltaba en
INSTALL. libtool RELINKEA el binario al instalar y ahí pierde el flag. El
patrón citado estaba a medias, y por eso el propio precedente mentía.
libarchive: mismo patrón, forma de fase con comillas dobles.
Control de lista de ficheros intacta: parted 23, libarchive 55.
parted b3:1f2046dde4dd · libarchive b3:238f1fdfc704
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mismo patrón de libtool: LDFLAGS="-all-static -no-pie" en compile Y en install.
El control duro (lista de ficheros vs sellado viejo + 0 NEEDED en TODOS los
ejecutables, si no revierte) hizo su trabajo: pcre2 salió del build con
pcre2grep y pcre2test todavía dinámicos y se revirtió sola. Sin ese control
habría entrado como "arreglada" — es la misma trampa de xz, que devolvía rc=0
con el artefacto mutilado.
sqlite b3:235b18d98f18 (8 ficheros) · libgpg-error b3:8ccbe32ccb41 (36) ·
fontconfig b3:ad960af29ef5 (65) — todas con la lista de ficheros intacta.
Quedan 20: 9 con forma de fase distinta (mandoc, libwebp, kbd, parted, sudo,
libarchive, lsof, samurai, pkgconf, libcap: van a mano), pcre2 y xz que
necesitan otro enfoque, y 5 Rust (helix, yazi, git-absorb, cargo-audit, tuc)
que arrastran libgcc_s por crt-static, no por libtool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mismo patrón que curl: LDFLAGS="-all-static -no-pie" en compile Y en install,
porque libtool relinkea al instalar.
Verificado con un CONTROL que xz obligó a añadir: no basta rc=0 + hash. El
build de xz con -all-static devolvió rc=0 y hash válido, pero el artefacto SALIÓ
SIN NINGÚN BINARIO (el viejo tenía xz, xzgrep, xzdiff, xzless, lzmainfo): con
-all-static, libtool no produce la lib compartida (liblzma.la -rpath) y el
enlace de los ejecutables se saltea EN SILENCIO. Un "éxito" que mutila el
paquete. xz queda revertida: necesita otro enfoque (separar la lib de los
binarios), no este patrón.
⇒ el criterio ahora compara la LISTA DE FICHEROS del artefacto nuevo contra la
del viejo, además de NEEDED=0. Los tres pasan: 15, 13 y 114 ficheros, idénticos
a sus sellados previos.
expat b3:e6990b3e555f · libpng b3:f65887051274 · libxml2 b3:c4c65c4b17b8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El frente contaba `compiler = "gcc"` y no miraba las FASES. bzip2 declaraba
compiler=zig-cc y decía "migrado de gcc, matar-gcc 2026-07-16" en la cabecera,
mientras su compile pasaba `CC=gcc`: migración cosmética, cambió el campo y no
el build. El Makefile de bzip2 además HARDCODEA CC=gcc, así que un `make` a
secas tampoco habría usado zig.
Barrido: 7 recetas invocan gcc en sus fases sin declararlo. 4 son escapes ya
conocidos (los 3 kernels + cmake); las otras 3 son deuda que nadie contaba:
bzip2 (ésta), pigz y cargo-edit.
De paso cumple link=static: el -static que hammer exporta (lib.rs:261) se
pierde si la receta no lo pasa al make de un Makefile custom.
OJO -static y NO -all-static: -all-static es flag de LIBTOOL (patrón de
jq/parted/shadow/procps-ng); bzip2 usa Makefile crudo y el flag llega tal cual
al compilador → "error: Unknown Clang option: '-all-static'".
Verificado: 0 NEEDED, corre en el host, comprime/descomprime, bit-repro
b3:48b91bb1b658 ×2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primera de las 28 que static-audit.sh destapó. libtool ignoraba el -static del
lab (lo lee como "preferí mis .a"), así que el curl sellado salía dinámico con
NEEDED libz.so.1 + libc.so — y libc.so es el soname de la musl de zig, que en
el host son 255B de linker script. Resultado: el artefacto NO arrancaba fuera
del sandbox ("Error relocating /lib/libz.so.1: __snprintf_chk").
Fix: LDFLAGS="-all-static -no-pie" en compile Y en install (libtool relinkea
al instalar); nunca en configure, donde rompería los link-tests.
Verificado: 0 NEEDED, corre en el HOST (curl 8.20.0, OpenSSL/3.5.4, zlib/1.3.1)
y hace HTTPS real (http=200). Bit-repro: b3:19a919b28c25 ×2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Destapado migrando procps-ng. libtool lee el -static del lab como "preferí mis
.a", NO como flag al linker: el binario sale dinámico y la receta jura que es
estático. Con gcc pasaba igual — no es regresión de zig, es un agujero que
nadie había medido.
Tres consecuencias medidas, no teóricas:
1. NO CORREN. El curl sellado en el host: "Error relocating /lib/libz.so.1:
__snprintf_chk: symbol not found". Su NEEDED libc.so es el soname de la musl
de zig; en el host /lib/libc.so son 255B de linker script. El artefacto sólo
funciona dentro del sandbox.
2. ARRASTRAN GCC. helix, yazi, git-absorb, cargo-audit y tuc traen libgcc_s.so.1
de Alpine DENTRO del binario. Deuda de matar-gcc que ningún compiler="gcc"
declara: invisible para el frente entero hasta ahora.
3. Un NEEDED es una entrada no declarada — lo que harkaq mide en build, pero en
RUNTIME. Rompe el cono de affected.py (SDD 17 §4): un CVE en zlib no
alcanzaría a un curl que se declara estático.
Script, no gate duro: fallar hoy rompe 28 selladas de golpe, varias del sistema
base (curl, util-linux, sudo). Primero se arreglan con evidencia, después se
cierra la puerta. Exit 1 mientras haya mentirosos ⇒ sirve de gate en CI cuando
la lista llegue a cero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
31 migradas. La justificación escrita ("gueto, como vim/nano/htop") había
caducado: vim/nano/htop ya estaban en zig-cc. Tres problemas reales, ninguno
"zig miscompila":
1. rpl_realloc: la musl que zig bundlea (estática) devuelve NULL para
realloc(p,0); AC_FUNC_REALLOC acierta y hace #define realloc rpl_realloc,
pero procps-ng no trae esa función (la pide por AC_LIBOBJ y ningún
Makefile.am usa @LIBOBJS@) ⇒ roto upstream en cualquier libc que conteste
"no". Se suprime el renombrado por cache vars; no afirmamos que realloc sea
GNU-compatible. Afectará a todo autoconf con AC_FUNC_REALLOC/MALLOC.
2. SIGSEGV: el binario salía musl-DINÁMICO con NEEDED libc.so. libtool lee el
-static del lab como "usá mis .a", NO como flag al linker ⇒ link=static
nunca se cumplió, ni con gcc. Fix con el patrón de jq/parted/shadow:
LDFLAGS="-all-static -no-pie" en compile Y en install (libtool relinkea al
instalar), nunca en configure.
3. UBSan: zig-cc lo activa por defecto; ps --sort=-rss aborta en sortformat.c
(offset sobre puntero nulo, UB genuino de upstream pero inocuo). Patrón de
libarchive/dwarves: -fno-sanitize=undefined.
Bit-repro verificado; 18 binarios responden y ps --sort da salida idéntica a gcc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sin parches ni flags: el config.sub de wget 1.25.0 ya conoce -musl* y el
configure no se atraganta con AR="zig ar". Bit-repro verificado (2 builds
desde cero, mismo hash) y la ruta openssl viva (descarga HTTPS real, exit 0).
Como link=static, el ELF no tiene sección dinámica: cero NEEDED ⇒ no arrastra
libgcc_s/libstdc++ de Alpine. El comentario viejo listaba gcc como parte de la
de-Alpinización, que era justo al revés; corregido.
30 migradas. Quedan 17: 12 Rust con sys-crate C (cc-rs invoca el compilador del
sistema desde los build-scripts), 4 matraca dura, procps-ng en vuelo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zlib-ng: el veredicto T5 hecho receta — dispatch SIMD en runtime en vez de
variantes v3; modo ZLIB_COMPAT, zlib.toml intocada (dep del frente rust).
dwarves: pahole para destrabar sched-ext; el kernel NO lo declara aún (el
análisis de repro BTF/pahole-skew va primero). Ambas al worker.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- harkaq-trace.c: fanotify FAN_MARK_FILESYSTEM — agnóstico de mount-ns, el
tracer en el HOST ve los open() de dentro del bwrap (ptrace lo deniega el
seccomp D4; audit sólo emite denegaciones). Crudo y tonto a propósito
(reparto Q1c); D9: sin fanotify ⇒ exit 3 SinEvidencia, jamás traza vacía.
- harkaq-trace-norm.py: canoniza (drop /proc /sys /dev /tmp /out /src, dedupe,
orden estable — la lección nº1 de META MODE) + atribución por dep con
--resumen = la señal de PODA (T1.4): dep declarada con 0 toques = grasa.
- VALIDADO: modo --fs como root (worker, 5s, sin tocar colas): capturó
exactamente zlib.h + 3×libz.a de un cat/head; laptop sin privilegio ⇒
SinEvidencia honesto. Piloto completo (traza de un hammer build real +
resumen de poda) pendiente de un rebuild natural en el worker.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El bug que declaró `busybox` en 29 recetas y rompió binutils: harkaq-suggest veía
`/bin/busybox` denegado, encontraba `recipes/busybox.toml` en el store y decía
"declarar dep: busybox" — sin mirar que la política YA lo concede (`ro /bin/busybox`:
el sandbox corre `sh -c` y /bin/sh→busybox ⇒ es CONTRATO, no dep).
Fix: harkaq-suggest ahora lee los paths CONCEDIDOS de la política (`ro`/`rw`/`list`),
no sólo los `# expect`. Si un path está concedido ⇒ categoría RUNTIME BASE: no se
declara, aunque el store lo provea.
Las 4 clasificaciones verificadas tras el cambio:
/bin/busybox → RUNTIME BASE (ya concedido; NO declarar) ← el fix
/usr/bin/make → DECLARABLE (declarar dep: make)
/usr/lib/libstdc++.so → IRREDUCIBLE (nadie lo provee)
/usr/bin/gcc en broot → DEUDA DE COMPILADOR (compiler=gcc)
/usr/bin/gcc en zlib → sin deuda (sonda esperada; zlib compila con zig)
Con esto puesto, busybox JAMÁS se habría declarado. Cierra el TODO que dejó el
revert: la campaña automática ya no puede cimentar contrato como dependencia.
Co-Authored-By: Claude Opus 4.8 <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>
Deps de Kconfig verificadas contra el tag v6.16.12 (kernel.org):
NTSYNC sin deps; SCHED_CLASS_EXT depende de DEBUG_INFO_BTF ⇒ tarea propia
(receta dwarves + repro de BTF). Re-sellado disparado en el worker.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El SDD 16 dejó abierto: '¿el vector page-cache de copy.fail sobrevive a un
ruleset que deniegue escritura a nivel de inode? Investigar antes de afirmar
nada'. Investigado en un fs AISLADO (loop en un worker efímero, nunca el fs del
usuario):
escribir al artefacto sellado → EPERM
dd como ROOT → Operation not permitted (ni root)
machacar los bytes POR DEBAJO (device)+leer→ Input/output error
El tercero ES el vector de copy.fail: modificar el fichero por debajo del fs. El
kernel detecta la corrupción AL LEER (el Merkle no cuadra) y rechaza la lectura.
⇒ fs-verity mata el vector. Landlock read-only no alcanzaba; fs-verity sí.
Costo medido, y corrige al SDD 17 §5: 'hash = identidad' es FALSO — fs-verity usa
Merkle SHA-256, hammer direcciona con BLAKE3 ⇒ son DOS hashes, no uno. Y ext4
exige -O verity Y blocksize = PAGE_SIZE (con 1024 el mount falla:
'Unsupported blocksize for fs-verity'). No es gratis, pero es barato para lo que da.
NO se tocó el fs del laptop (habilitar verity pediría tune2fs sobre la partición
del usuario): el experimento corrió en un loop device de un worker descartable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`scripts/affected.py <pkg> [--agenda] [--sellados]`. Un CVE no obliga a
reconstruir el catálogo: obliga a reconstruir el CONO de lo que depende del
paquete, y nada más. El grafo declarado ya lo sabe.
CVE en zlib → 65 de 761 recetas (8%) — el resto NO se toca
CVE en expat → 20 de 761 (2%), las 20 selladas = el trabajo real
agenda: fontconfig → fcft → cairo → pango → dbus → wayland…
Es el `dirty_cone` de `dataflow` (crate no_std de tawasuyu) + `topo_order` como
agenda (una dep antes que su consumidor), calculados sobre las deps declaradas.
`--sellados` separa lo que hay que reconstruir de verdad (tiene artefacto) de lo
que no existe aún.
CAE SOLO DEL INVARIANTE: las deps están declaradas por hash ⇒ el cono es
computable y EXACTO. Una distro sin clausura declarada tiene que adivinar o
reconstruir todo por las dudas. Ésa es la tesis del SDD 17.
Honestidad grabada en el propio output: la frontera es exacta respecto de lo
DECLARADO. Lo que una receta usa sin declarar queda FUERA del cono — y ese es el
agujero que harkaq cierra (SDD 16). La campaña de esta semana declaró ~84 deps
que el kernel midió y nadie había escrito: sin eso, este cono habría sido
optimista. **El cono es tan bueno como la clausura**, y por eso los dos frentes
se necesitan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El #3 decía "la clausura medida por harkaq en build = permisos del binario
instalado". MEDIDO: es falso en dos ejes, y por eso NO se implementó así.
1. SUJETO: la clausura de BUILD (zlib.h, make, gcc) no es la de RUNTIME. Un build
lee headers; el binario resultante no. Dos clausuras, dos sujetos.
2. GRANULARIDAD/CRIPTO: lo que la ConcesionCapacidad firma NO es card-core::
Permissions (struct) sino format::Permisos = u32 bitmask, dentro de
`mensaje_capacidad` = hash(32)||permisos_le(4) = 36 bytes canónicos, zero-alloc,
con espejo no_std en wawa-kernel (Ring 0). Meterle paths rompería TODAS las
firmas y contradice el "capacidad = frontera física, no tabla" que el propio
plan cita como referencia.
⇒ Camino elegido: la concesión firmada QUEDA INTACTA (frontera gruesa, Ring 0) y
la clausura granular se deriva por MEDICIÓN como política Landlock en userspace,
donde Vec<String> sí cabe. Coexisten: el kernel verifica la frontera, Landlock
aplica el detalle.
runtime-policy.sh: corre el binario bajo la jaula con política mínima DERIVADA
(harkaq-base-closure, no a mano) y resta el BASELINE del lanzador (el sh deniega
locale/ld.so.cache; sin restarlo, la política del binario se lleva esa basura).
Demo real — htop (musl-estático): baseline 3 paths del sh; htop --version no tocó
NADA propio ⇒ su política de runtime es sólo `ro <su binario>`. Medido, no supuesto.
htop tiene 0 NEEDED: para un estático la política NO sale de las libs, sólo de
correrlo — lo que confirma que la técnica de harkaq es la única vía para el #3.
+ FIX del lector (lo cazó el chequeo anti-pérdida `nº ACCESS == denials`): descartaba
los ACCESS previos al canario porque hasta verlo no sabe su domain= — pero el sh
deniega ANTES del probe y esas son suyas. El kernel contaba 4 y el lector recibía 1.
Ahora se bufferizan con su domain y se rescatan retroactivamente al ver el canario.
Sin ese chequeo habría emitido una política con 1 de 4 accesos, perfectamente creíble.
(Primer intento de fix —drenar el socket al ver el deallocated— era plausible y NO
era la causa: el contador siguió en 4 vs 1.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El #2 de SDD 17, y encadena con el #1: cuando da DIVERGENCIA, esto
dice qué difiere y por qué. Hoy debuggear una no-reproducción es artesanal; esto
la vuelve mecánica y legible por máquina (lo que el bucle agéntico necesita).
Algoritmo (cruce con de tawasuyu): árbol = grafo blob+árbol hasheado ⇒
dos subárboles idénticos colapsan al mismo hash ⇒ descender SÓLO donde difiere.
O(diferencia), no O(tamaño).
Clasifica la causa, que es lo que decide la acción: solo-en-A/B (no-determinismo
de estructura) · tamaño · build-id · timestamp · path-embebido · codegen ·
contenido.
Probado: control (A vs A) → 'árboles IDÉNTICOS' ✓. Caso real (bzip2 gcc vs zig):
de 21 ficheros sólo difieren los 4 binarios → causa .
DOS VECES tuve que arreglar el diagnóstico por confundir SÍNTOMA con causa:
1. Chequeaba build-id PRIMERO ⇒ reportaba 'build-id' cuando la causa era el
codegen. El build-id es un HASH DEL CONTENIDO: difiere siempre que difiera
el código.
2. El umbral era '<1% del binario' — sonaba bien y clasificaba 1196 bytes como
build-id. Un build-id son 20 bytes (sha1). Atado al TAMAÑO REAL (<=64).
Un diagnóstico plausible no es un diagnóstico correcto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El top-1 de SDD 17 (mayor ventaja competitiva por esfuerzo). N builders
INDEPENDIENTES reproducen el mismo hash ⇒ el binario es confiable SIN FIRMA.
Convierte la repro de QA en primitiva de distribución: cualquiera puede ser
mirror, nadie puede envenenar el repo.
scripts/consenso.sh recipes/bzip2.toml 2
builder 1: hub local (Artix) → b3:86a33b76…
builder 2: worker efímero (Ubuntu) → b3:86a33b76…
✅ CONSENSO (2/2, bit a bit)
Dos máquinas físicas, dos distros, mismo hash byte a byte. Nix/Debian no pueden
ofrecerlo (repro incompleta). Cae SOLO del invariante que hammer ya paga — que es
la tesis del SDD 17: buscar qué cae del invariante, no qué frontera agregar.
Lo originó una observación accidental: al hacer el rollout de cmake noté que el
laptop y el worker daban el MISMO hash sin que nadie lo buscara. Eso ya era
consenso ocurriendo; esto sólo lo formaliza.
Divergencia ⇒ exit 1 y apunta a why-differs (§2): una divergencia YA es
información (un canal impuro encontrado). NO firma ni publica: sólo el veredicto.
El log de transparencia (fork-proof: hash-encadenado + detección de equivocation)
ya está escrito en tawasuyu — no hay que construir un rekor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Debí evaluar esto ANTES de migrar, no después: las 29 tienen 36 consumidores,
incl. toda la cadena GUI (cairo/gtk4/pango/harfbuzz/fontconfig/libadwaita) +
mesa/mirada. 6 migradas son deps de ese stack, que por regla no se rebuildea en
el laptop.
No se pudo construir la GUI completa en ninguna máquina (laptop: python3 no
construye — gotcha conocido, verificado que es PREEXISTENTE revirtiendo mis deps;
worker golden: busybox no construye — store parcial). Así que se verificó lo que
decide el link: el ABI.
freetype 90 · expat 12 · libpng/libpng16 385 · libffi 68 símbolos globales
→ IDÉNTICOS entre gcc y zig ⇒ la GUI linkea igual.
Dos falsos positivos cazados al medir: . comparaba libpng.a contra
libpng16.a (libs distintas), y cuenta símbolos INTERNOS de los
.o que difieren entre compiladores sin tocar el ABI (294 'diferencias' espurias).
Lo correcto: mismo fichero + --extern-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PLAN-KIKIN §4.bis + HANDOFF-KIKIN-DESDE-HAMMER: el sustrato que kikin asume
(J4 = io_uring, J4b = Landlock/seccomp del anfitrión, eBPF = observabilidad)
tiene que venir COMPILADO en los kernels que hammer distribuye — «al azar del
defconfig» no es un contrato.
- linux-generic 7.1.2: re-sellado en el worker (5c5ef06c) y VALIDADO en QEMU:
config =y los cuatro, landlock_create_ruleset/io_uring_setup/bpf_prog_load
en kallsyms, y el re-test de input de mirada pasa (5 dispositivos, salida de
emergencia procesada). Imagen mirada-usb re-empaquetada con este kernel.
- linux-metal: mismos flags; se re-sella en el próximo build de imagen metal.
- linux.toml (soberano): NO tocado — su of_tree es load-bearing del
selfhost-verify; deuda anotada en la receta para el próximo re-ancle.
- SECURITYFS queda fuera (introspección opcional; harkaq usa el probe
landlock_create_ruleset(VERSION), que no lo necesita).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contrato PLAN-KIKIN §9 (respondido en HANDOFF-KIKIN-DESDE-HAMMER.md): mirada
escribe /run/hammer/boot-select y dispara el reboot SIN salir; /run es tmpfs,
así que la selección se aplica en el apagado o nunca. arje-zero corre esto en
toda secuencia de apagado:
- sin fichero o vacío → no-op limpio (exit 0)
- con selección → activa (rollback E4) y CONSUME el canal; si falla, el fichero
queda para diagnóstico
- --select parametrizable (testeable); 3 tests nuevos (d/e/f)
- 'boot menu' documentado como HARNESS de dev/VM: en producción mirada no sale
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cerré el frente C-puras. Las 4 que no migran son matraca DURA con gcc
justificado (no inercia):
file → el binario zig SEGFAULTEA generando magic.mgc (persiste con -O0
⇒ codegen, no optimización)
linux-pam → 'cannot run C compiled programs' (binario zig no corre)
libgcrypt/libsodium → no construyen (asm criptográfico)
De 47 compiler=gcc: 29 migradas + 4 matraca dura (gueto gcc legítimo) + 11 Rust
diferidas + 3 no medidas. compiler=gcc: 47 → 18.
harkaq hizo el ciclo completo: reveló las 47 (invisibles bajo 'gcc esperada'),
midió cuáles migran, y el residuo quedó JUSTIFICADO con diagnóstico, no supuesto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
arje inserto StopCardFromDisk en el medio de BusRequest el 2026-06-26 y
corrio Subscribe a indice 14 — el puente estaba suscribiendose con el byte
viejo desde entonces (roto en silencio; lo delato el golden test de arje-bus
el 2026-07-16, lado tawasuyu 41f4accc7). Ademas el espejo de BusEvent no
conocia EnteParked/EnteRefloored: se agregan como consumo-sin-senal
(to_lifecycle -> Option). Tests unit+e2e actualizados y verdes.
Las 2 'sin confirmar' del balance: el smoke-test no las verificaba por ser libs
sin binario, pero generan la lib con zig (libevdev.so, libmtdev.a). Migradas.
mtdev queda COMPLETAMENTE de-Alpinizado: config.sub soberano (patch de esta
sesión) + zig compiler ⇒ cero dependencia de Alpine. Era la única deuda de
soberanía del barrido harkaq y ahora está cerrada por entero.
Total matar-gcc: 29 recetas C migradas. Quedan 18 compiler=gcc (4 matraca-C
dura + 11 Rust + 3 no medidas).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Faltaba persistir en harkaq-farm-setup.sh las 2 sondas que añadí a base.policy
al revisar needs-review (cpp=frontend gcc, getent=NSS musl). Sin esto, un worker
nuevo derivaría una base.policy sin ellas y volvería a marcar irreducibles
falsos (dhcpcd/strace).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
curl (dep de muchos) y libevent verificadas en laptop: construyen+corren con zig.
Total sesión: 27 recetas C migradas gcc→zig-cc.
BALANCE de las 47 que harkaq nombró:
27 MIGRADAS ✅ (deuda histórica: zig 0.13/0.16 segfaultaba, zig mejoró)
4 matraca real: libgcrypt/libsodium (crypto asm), file (magic), linux-pam
2 sin confirmar: libevdev, mtdev (libs, smoke-test no verifica)
11 Rust diferidas (gcc para el C embebido de sys-crates, otra evaluación)
matar-gcc pasó de '47 invisibles' (harkaq las reveló al leer el compiler=) a
27 cerradas + cola nombrada. Reporte: tandas/needs-review-harkaq/migracion-zig.md
OJO: re-hashea las 27 del índice firmado 748 ⇒ re-firma en próximo packaging.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda tanda. Candidatas que el VPS marcó migrable; re-verificadas en el LAPTOP
(store completo, fiable) build+corre — 14/14, cero matraca:
freetype jq libassuan libgpg-error libssh2 libuv libxml2 nano pcre2 pigz socat
tig tmux vim
pcre2 tuvo una discrepancia transitoria (falló en una prueba local temprana con
"linker version script", migró en el VPS); re-verificado limpio en el laptop:
construye Y corre. Era estado transitorio, no incompatibilidad.
Confirma el patrón: el VPS acierta en las MIGRABLES (build+corre son evidencia
real); sólo erraba en las matraca (build-falla espurio por store parcial). La
medición fiable es el laptop.
Total matar-gcc esta sesión: 25 recetas C migradas (11+14). El frente pasa de 47
compiler=gcc a ~22 (las 25 migradas + las que faltan medir + las 11 Rust). OJO:
re-hashea, índice firmado 748 necesita re-firma en el próximo packaging.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primera tanda del rollout aprobado. harkaq nombró 47 recetas compiler=gcc;
migra-zig.sh midió que las C-puras migran a zig (~85%). Estas 11 se verificaron
en el LAPTOP (store completo, fiable): construyen con zig-cc Y —las que tienen
binario— corren limpio.
bzip2 expat json-c gzip htop less libpng libyaml zstd libffi xz
El compiler=gcc era deuda histórica: se marcaron con zig 0.13/0.16 (que
segfaultaban C clásicos), zig mejoró, el gcc quedó por inercia. Verificado
in-place: bzip2→b3:86a33b76, json-c→b3:9cbe76e3.
11 dependencias menos del gcc de Alpine. Quedan ~25 C-puras candidatas del VPS
por verificar en el laptop (el store parcial del VPS daba build-falla espurios
por deps faltantes — la medición fiable es la del laptop) + las 11 Rust
(sys-crate C, otra evaluación).
OJO: re-hashea estas 11 (estaban en el índice firmado 748); el índice necesita
re-firma en el próximo packaging.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Causa raíz del viaje 2026-07-16 (pantalla viva, teclado/mouse muertos): el apk
de mesa arrastra eudev-libs y el 'cp *.so*' del overlay pisaba el libudev.so.1
de libudev-zero. Sin udevd, el libudev de eudev no expone ID_INPUT y libinput
ignora TODOS los dispositivos en silencio; la GPU no lo delata porque smithay
la encuentra por syspath. Reproducido y verificado A/B en QEMU (usb-kbd):
eudev = 0 dispositivos; libudev-zero = 5 y Ctrl+Alt+BackSpace corta el
compositor al instante.
- 5c-bis: restaurar libudev-zero tras el overlay + aserción cmp (regresión = build roto)
- MIRADA_DEBUG_DIR → run-NNNN del pendrive (eventos.log y panics ya no mueren en RAM)
- imagen re-empaquetada con el fix + gpt en la cmdline
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
¿Las 47 compiler=gcc que harkaq nombró son factibles de migrar a zig, o hay que
matracar? MEDIDO, no adivinado.
migra-zig.sh: para cada receta, build con zig-cc + SMOKE-TEST del binario —
porque el motivo del gcc era segfault en RUNTIME, no fallo de build; un artefacto
que sella pero segfaultea es peor que gcc. Idempotente, acumula por lotes (probar
47 no cabe en un timeout).
Muestra de 13 C-puras: 11 MIGRAN (bzip2 expat json-c gzip htop less libpng
libyaml zstd libffi xz), 2 MATRACA (pcre2: linker version script que zig ld no
soporta; file: subdir magic). ≈85% yield.
VEREDICTO: es GRANJEADA para las ~36 C-puras. El compiler=gcc era deuda histórica
— marcadas con zig 0.13/0.16 (que segfaultaban C clásicos), zig mejoró, el gcc
quedó por inercia. ~85% migran solas; el residuo (~15%) es matraca individual por
causas concretas del build-system.
Las 11 Rust con sys-crate C son otra evaluación (gcc para el C embebido de
libgit2-sys, no el Rust) — se deja para después.
Rollout = decisión del usuario (re-hashea recetas del índice firmado 748). Correr
migra-zig.sh sobre las 47 en la granja VPS da la lista final; promover las
migrables re-firma el índice. Reporte: tandas/needs-review-harkaq/migracion-zig.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El firmware del tester reparo el GPT dd-eado en el POST y dejo el header
primario invalido (entradas apuntadas a LBA 2016, presentes en LBA 2):
Linux sin gpt en cmdline no cae al respaldo, no vio particiones y la
telemetria MIRADALOG murio en RAM. Con gpt el kernel usa el respaldo.
Documentado: greeter por nouveau GL validado en metal; input muerto queda
abierto con el plan de evidencia para el proximo viaje.
El límite del modelo que mtdev destapó, cerrado. harkaq clasificaba /usr/bin/gcc
como "sonda esperada" SIEMPRE, así que marcaba `Hermetico` a las recetas que
compilan CON el gcc de Alpine (compiler=gcc = CC=gcc, musl+GNU ld). Estuvo ciego
a la deuda de compilador todo el barrido.
Fix en harkaq-suggest: 5º arg = la receta medida. Si usa compiler=gcc, los
frontends de gcc (gcc/cpp/c89/c99/g++/cc/ld/as) dejan de ser "esperada" y pasan a
DEUDA DE COMPILADOR (matar-gcc). Para compiler=zig-cc (default, 714 recetas) gcc
sigue siendo sonda de configure — esperada, como antes. Verificado: mtdev(gcc) →
deuda de compilador; zlib(zig) → sin deuda.
EL HALLAZGO: matar-gcc no es "{kernel, cmake}" como creía la memoria del proyecto.
Son 47 recetas (36 C puro + 11 Rust con sys-crate C) que dependen del gcc de
Alpine. harkaq lo reveló sólo cuando aprendió a leer el compiler= de la receta —
antes gcc-como-sonda-esperada ocultaba una deuda mayor que toda la que sí detectó.
Reporte: tandas/needs-review-harkaq/deuda-compilador-matar-gcc.md.
Cada una usa gcc porque zig-cc la miscompila (documentado en su receta). Acción
por receta: migrar a zig cuando zig mejore, o aceptar el escape como deuda
conocida. Ahora es una deuda NOMBRADA y contada, no invisible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El único caso de deuda de soberanía del barrido: mtdev copiaba el config.sub/guess
del autoconf de ALPINE (/usr/share/autoconf/build-aux/) porque el suyo (1.1.6) no
conoce *-musl. harkaq lo cazó como lectura no declarada del rootfs.
CERRADO con recipes/mtdev-config-sub-musl.patch: 11 líneas, +`-musl*` a la
whitelist de OS del config.sub DEL PROPIO TARBALL. Ahora es input soberano
(hasheado con la receta), sin leer Alpine. Se quitó el `cp /usr/share/autoconf/...`
del configure. Verificado: mtdev construye (b3:47708e40) sin tocar /usr/share/autoconf.
Método (con un tropiezo honesto): casi afirmo que el `cp` era espurio porque el
config.sub en work/sources/mtdev daba exit 0 con musl — pero ese source estaba
MUTADO por un build previo (el cp lo había reemplazado por el de Alpine). El
tarball ORIGINAL fresco NO conoce musl (exit 1). Casi valido sobre un source
contaminado; me salvó chequear el exit code, no el output.
DESCUBIERTO AL CERRARLO: mtdev tenía DOS deudas de Alpine, no una. También usa
`compiler = "gcc"` (gcc de Alpine, no zig). Bajo la jaula la política deniega
/usr/bin/gcc (sonda esperada) y el configure da "C compiler cannot create
executables". Es parte de matar-gcc (kernel, cmake, mtdev), frente aparte.
LÍMITE DEL MODELO (nuevo, documentado en needs-review): harkaq clasifica gcc como
"sonda esperada" SIEMPRE, así que marca la fase Hermetico — pero para una receta
compiler=gcc, gcc es ESENCIAL y el build no compila sin él. harkaq no distingue
"gcc sondeado (ignorable)" de "gcc = el compilador". Caso límite real del
clasificador; no invalida el cierre de config.sub (producción no usa la jaula).
make declarado en mtdev (deuda declarable restante). config.sub fuera de
needs-review; queda anotado el gcc-compilador.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>