Corre una app KDE del rootfs hidratado como cliente wayland ANIDADO en el compositor
mirada vivo (WAYLAND_DISPLAY=/run/mirada-wayland), vía bwrap overlay (alpine+kde-rootfs
como /). Cliente, no master: renderiza por renderD128/iris, cero riesgo al display.
Resuelve dbus de sesión (root-en-userns + machine-id + /run/dbus). konsole y dolphin
verificados corriendo en vivo. Fix portabilidad: run-plasma-headless.sh += --setenv PATH.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El comentario justificaba openrc como 'el supervisor de servicios que le falta a arje':
falso. arje-zero es autónomo (PID1 + gestiona servicios). openrc es un init ALTERNATIVO
que la distro ofrece y COMPITE con arje por PID1, no una capa que se apila. Reencuadrado
como el patrón para el resto de la oferta multi-init (runit/s6/dinit). Corrige también la
nota de deps: libcap NO es opcional en Linux (dependency incondicional en meson.build).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openrc es el supervisor de servicios (rc-service/rc-status/rc-update/start-stop-daemon)
que faltaba para una distro instalable — arje-zero es PID1, openrc gestiona /etc/init.d.
El build fallaba por libcap: meson.build lo pide incondicional en Linux (dependency
'libcap' >=2.33, sin -Dlibcap). Añadido → binarios estáticos-musl sellados.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
libXft es el único de los 11 que no sella. Falla por PIC (freetype/libpng/expat
estáticas no-PIC dentro de un .so), riesgo que su propio header ya predecía. NADIE
lo consume: plasma-workspace (su único declarante) selló SIN él porque KDE es Wayland.
Forzarlo sellaría un artefacto muerto. A .deferred/ para que el loop no lo reintente;
la cadena de deps .pc quedó resuelta (libpng+expat), sólo resta el muro PIC (Capa 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El crontab */30 no dispara cuando el laptop bootea en mirada (PID1=arje-zero, sin
crond). Envuelve el one-shot cosecha-cron.sh en un loop setsid — el mecanismo que sí
corre sin root y sobrevive al fin de sesion (no al reboot).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raíz de una noche de KDE atascada: la golden del 29-jun horneaba un hammer SIN el fix del
overlay lowerdir (e69eaaa, 12-jul) ⇒ toda receta de muchas deps (kio=52, kwin, plasma-*)
desbordaba el límite de 4KB de mount options y el sandbox ni arrancaba. 43 recetas 'en cola'
que en realidad reventaban al instante. El source llega fresco por farm-sync pero nadie
recompilaba. Ahora el worker-loop hace 'cargo build --release --bin hammer' al arrancar
(~24s cacheado). Binario del worker vivo ya rebuildeado a mano y verificado: kio cruza el
mount (funde 52 deps en una capa) y configura.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chequeo manual por si venís antes del cron o no corrió: salud del worker y qué muele,
barra de avance KDE desde el grafo, y últimas líneas del ciclo de cosecha. Filtra el
self-match del pgrep (el ']+.toml' que se colaba).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El init/service-manager de Gentoo y Alpine — musl-clean por diseño (Alpine lo corre sobre
musl en prod). Enlace static-musl vía zig-cc; deps opcionales (audit/selinux/pam/libcap)
desactivadas para clausura mínima. Encolado en incoming/; el worker lo muele tras la pasada
KDE. Es la pieza de supervisión que falta para la distro instalable (arje-zero=PID1 + openrc
sobre /etc/init.d). hash dry-run: b3:78502dcb.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El worker efímero muele KDE 24/7; este cron en el laptop recoge su store sellado
(worker->laptop, CAS merge seguro), siembra recetas nuevas (laptop->worker) y regenera
el grafo de estado, commiteando SÓLO docs/state/*.json (el avance que el humano sigue).
NO promueve incoming-kde->canónico (decisión de clasificación humana). Robusto a
worker-ausente (dead-man ya lo mató por idle). KDE: 66/206 sellado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El grafo buscaba store/<hash>-<basename> pero el artefacto se sella por el name INTERNO de la receta
(qtbase.toml → name=qt6-qtbase → store/<hash>-qt6-qtbase). Las qt6-* KDE salían 'debt' aunque
estuvieran selladas. Fix: nodo lleva iname=d['name']; state_of busca por iname. Las aristas de deps
siguen por basename (así resuelve resolve_dep_path). qtbase ahora sale sealed correctamente.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El --disable-jit anterior movió el error de pcre2_jit_compile.o a pcre2_compile.o (pso_list): el
fondo NO era el JIT, era que los objetos del .a se compilaban SIN -fPIC. qtbase construye Qt SHARED
(BUILD_SHARED_LIBS=ON, libQt6Core.so) y linkea libpcre2-16.a DENTRO de ese .so; un .a no-PIC lleva
relocaciones absolutas R_X86_64_64 que ld.lld rechaza en un objeto compartido. El propio
pcre2-shared.toml ya lo documentaba: 'el canónico es --disable-shared SIN --with-pic ⇒ su .a no
enlaza dentro de un .so'.
Fix: --with-pic + CFLAGS=-fPIC ⇒ objetos position-independent, linkables en .so y en PIE. Convive
con el -no-pie del ejecutable pcre2grep (el audit static lo quiere). JIT queda off igual (W^X +
tablas frágiles). VERIFICADO: qtbase pasó el punto de fallo (objeto 400) y compila de largo (490+),
sin el 'relocation cannot be used against local symbol'.
Es un fix raíz del modelo static↔dinámico (Capa 0 KDE): cualquier consumidor .so del pcre2 canónico
lo necesitaba.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
qtbase falló al linkear: ld.lld 'relocation R_X86_64_64 cannot be used against local symbol' en
libpcre2-16.a(pcre2_jit_compile.o). El JIT de PCRE2 emite relocaciones absolutas en .rodata (sus
tablas de código generado) que son incompatibles con PIE — y TODO binario static musl de hammer se
enlaza PIE. No es un problema de Qt: es un landmine para el modelo entero; cualquier binario PIE que
linkee pcre2 con JIT lo pisa.
Fix raíz: --disable-jit. El regex queda un pelo más lento y la API NO cambia (pcre2_jit_compile
devuelve error si no hay JIT, los consumidores lo manejan). Rehashea pcre2 y sus dependientes, pero
ya estaban en deuda (lo re-sellé este ciclo con el arreglo static). Destraba qtbase → la raíz del
árbol KDE (↑117).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tanda de base libs KDE construidas en el laptop mientras qtbase compila en background. qtbase (la
raíz, ↑117) va por 400/2163 objetos. libsndfile falló por fetch de red (no código).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MOTIVO: 'estábamos haciendo kde'. La cola recipes/incoming-kde/ (206 recetas) era punto ciego del
grafo (sólo miraba recipes/ top-level). Modo --kde: build-state.py/build-state-view.py cubren la cola
KDE (sombreando canónicas homónimas sibling-first, como el sandbox) → build-state-kde.{json,html}.
CAUSA de la deriva (193/206 en deuda): fui YO — esta sesión re-sellé pkgconf y samurai (arreglos
static), y 194 recetas KDE declaran pkgconf, 142 samurai ⇒ re-hash en cascada de todo el árbol. El
sistema determinista funcionando: cambia un input base, cascan los dependientes. Deuda legítima.
DESBLOQUEO (el 'GUI rompe en el laptop' era sólo cairo/pango, NO la base): construida en el laptop
toda la base KDE guiado por el orden de ataque del grafo — extra-cmake-modules, util-macros,
xorgproto, xcb-proto, wayland, libdrm, dbus, icu4c, util-linux, fontconfig, pixman, glib, freetype,
harfbuzz, fribidi, libXau/libXdmcp, la cadena X11 (xtrans/libxcb/libX11/libXext/libXrender/libXfixes/
libXi), la familia xcb-util, libxkbcommon, wayland-protocols, mesa (24.0.9 iris-only, sin LLVM), y
los -shared. KDE 9→39 selladas. qtbase (↑117, la raíz) quedó DESBLOQUEADO y compilando.
DISCO: el build llenó el disco (100%). Liberados 82G borrando work/sources (57G, se re-extrae solo)
y .farm-harvest (26G, verificado 100% redundante en el store, CAS).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Señal fuerte para Rust: la receta copia su binario de target/release/ en las fases. Medido 225
recetas, 0 con dep go ⇒ sin falsos positivos con Go (findutils entra bien: es uutils/findutils, find
en Rust). LÍMITE: un crate BuildSys::Cargo PURO sin fase install propia (zellij) no deja rastro en el
TOML y cae a 'c' — sólo se ve bajando la fuente, que el generador no hace.
Efecto: clases c 322→141, rust 45→226 (la mayoría eran Rust auto sin fase cargo explícita). Y la
LECTURA de la deuda cambia: la deuda C REAL es sólo 8 (casi toda saldada esta sesión), no 74. Lo que
queda es Rust (15, CLI pesados → granja), GUI (29 → granja), Go (5 → worker), kernel (4).
musl sellado (base). Avance: sealed 703→704, debt 53→52.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tres tandas más de C-base de sistema, todas verifican (MIENTEN 0): perl (background, desbloquea 8),
la cadena curl/ca-certificates/gnupg, y bwrap dosfstools e2fsprogs gzip htop iproute2 iputils jq kbd
lz4 libsodium libuv mandoc parted pciutils procps-ng rsync shadow strace usbutils dhcpcd git vim
wget openssh tmux xorriso wpa_supplicant libssh2.
Avance acumulado de la sesión: sealed 647→703 (+56), debt 109→53. La deuda C-base cayó 74→18, y de
esos 18 la mayoría son Rust CLI mal-clasificados como 'c' (amp/broot/delta/gitui/zellij…) + musl.
La deuda que queda es GUI (29, granja), Rust (5), kernel (4), go (5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda tanda de sistema-base en el laptop, todas C puras listas: libudev-zero mtdev libevdev npth
libusb libevent libassuan libksba socat less nano sed gawk doas. MIENTEN 0 en las link=static.
Avance acumulado de la sesión: sealed 647→671 (+24), debt 109→85. La deuda C-base bajó 74→50; la
GUI (29) sigue intacta para la granja, como manda el orden de ataque (glib/wayland/pixman ↑).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Construidas y selladas en el laptop (C-base, listas, no-GUI, sistema): zstd (desbloquea 10), xz,
tar, tree, tzdata, tig, scdoc, when. Todas verifican: MIENTEN 0 en las link=static. Es progreso
real visible en el grafo — el resto de la deuda de alto impacto es GUI-céntrica (glib/wayland/pixman)
y va a la granja, como muestra el orden de ataque.
Snapshot del grafo actualizado: sealed 647→655, debt 109→101, never 8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El grafo ahora computa, sobre las dependencias, dos campos por receta:
blocked_by — deps que están en deuda (lo que impide construirla ya)
unblocks — cuántas recetas EN DEUDA la declaran como dep (su impacto de desbloqueo)
Una receta en deuda sin blocked_by es construible YA; ordenadas por unblocks desc, dan el orden que
libera el grafo más rápido. La vista muestra la pastilla ↑N en las filas listas y ordena por ahí.
Top de impacto (todo el stack GUI concentrado, más zstd/perl de C-base):
glib ↑14 wayland ↑13 pixman ↑12 libdrm ↑11 fontconfig ↑11 zstd ↑10 libxkbcommon ↑9 perl ↑8
Es la guía para cuando se levante la granja: construir esas primero desbloquea el grueso.
De paso, diagnóstico de las 8 'never' (ninguna es cruft ni bug): dwarves BLOQUEADA-documentada
(necesita libdw, elfutils da sólo libelf a propósito — sub-proyecto elfutils-libdw); llimphi-counter
es un EJEMPLO/plantilla intencional; las otras 6 son imports Go/Rust que van al worker. Y confirmado
parseando: 0 recetas con FIXME real en el campo sha256 (el grep decía 43, todas comentarios) — otra
vez grep miente, el grafo (parsea) dice la verdad.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dashboard self-contained (sin recursos externos, tema claro/oscuro) que rinde build-state.json para
VER y SEGUIR: barra de avance del corpus, tiles por estado, reparto de deuda por clase, y los huecos
en ORDEN DE ATAQUE. Ese último es el uso accionable del grafo: cada receta en deuda muestra sus deps
coloreadas por estado; una fila 'lista' tiene todas sus deps al día (construible ya), una 'espera N'
depende de N recetas también en deuda. Foto actual: 117 en deuda, 71 listas ahora.
Publicado como Artifact para verlo en el navegador; el HTML también queda en el repo (abrir con
file://). Regenerar ambos: scripts/build-state.py && scripts/build-state-view.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El estado real del build vivía disperso —en mi cabeza, en docs que envejecen (matar-gcc decía 47,
eran 16), en el store (que guarda TODOS los sellados históricos, no el vigente)— y cada medición a
mano mentía distinto. Este generador lo deriva de la ÚNICA fuente de verdad (recetas + hammer hash
+ store) a un JSON firme. El git diff de ese fichero ES el avance entre dos corridas: qué se saldó,
qué se rompió, qué cambió de estado.
NODO = receta {name, class, link, compiler, deps[], hash, state}:
sealed — el artefacto del hash VIGENTE está en el store (al día)
debt — hay sellados históricos pero ninguno vigente (cambió, falta rebuild)
never — sin ningún sellado
unhashable — hammer hash falló (hueco real)
ARISTA = dep de build. El grafo CIERRA (0 deps huérfanas) y topo-ordena sin ciclos.
Foto inicial (764 recetas): sealed 647 | debt 109 | never 8. Deuda por clase: c=74 go=5 gui=29
kernel=4 rust=5. Clases: go 362, c 322, rust 45, gui 30, kernel 5.
Validado contra lo que sé de esta sesión: samurai/which sealed, curl/openssl debt, helix sealed
(rust/gcc), naabu sealed (dynamic/go), mesa debt (gui), linux debt (kernel). Todos correctos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Las 7 recetas del stack gráfico (libdrm/mesa/meson/samurai/seatd/wayland/wayland-protocols) +
6 patches eran imports crudos de Alpine, redundantes: las 7 ya tienen receta CANÓNICA en recipes/
(mesa pineada a 24.0.9 iris-only A PROPÓSITO, no la 26.1.1 cruda con FIXME-sha256). Su trabajo
aterrizó por la vía canónica (7131cd4) hace 3 semanas; la cola quedó de cruft rompiendo cada ciclo.
fa45978 las sacó de QUEUES creyéndolas de 'otro agente' (5126a8b). Confirmado que NO hay otro
agente ⇒ borradas. recipes/incoming/ vuelve a ser cola de staging general y REGRESA a QUEUES; el
guard ls-vacío la salta si no hay nada. recipes/incoming/.deferred/ (24 recetas aparcadas con
diagnóstico, git con muro en libgit.a) NO se toca: el glob de QUEUES es top-level, no la muele.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El nuevo 'sin artefacto' del static-audit (deuda de rebuild ya no enmascarada) son 92 recetas
link=static cuyo hash VIGENTE no está sellado: el trabajo reciente (matar-gcc, static, harkaq)
cambió recetas base sin re-sellar, y cada cambio re-hashea en cascada todo lo que las declara.
Este script las salda. Calcula la deuda EN VIVO con 'hammer hash --check' (nunca una lista que
envejece), y construye cada receta NO-SELLADA. NO necesita orden topológico: 'hammer build' arrastra
sus deps recursivamente (construir curl construye openssl+perl primero); las cache-hit son instant.
Pensado para el WORKER (store completo, toolchain que no rompe el stack GUI): SKIP_GUI=1 por default
salta cairo/pango/gtk… que fallan en el laptop por zig-skew. Reparto medido: 76 C-base + 16 GUI.
Verificado end-to-end SIN quemar el laptop: modo DRY lista la deuda; y construí UNA receta diminuta
(which: NO-SELLADO → build 58s → SELLADO, hash vigente idéntico, estática de verdad en el audit).
El ciclo build+re-check del script es correcto. which quedó saldada de paso (deuda 76→75).
Confirma la decisión de usar la granja: 58s × 76 con openssl/gnupg/perl pesadas = 2-4h de laptop.
Cuando levantes la granja: farm-up, correr esto en el worker (SKIP_GUI=0 para incluir el GUI),
farm-down.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El agujero que dejé señalado 3 veces esta sesión: nada podía saber el sellado VIGENTE de una receta
sin construirla, así que static-audit.sh auditaba el más reciente por mtime (ls -dt) y acusaba a
recetas ya sanas (dbus/libnl) por un sellado anterior a sus flags.
FIX = subcomando `hammer hash <receta> [--check]`. Calcula el ArtifactHash puro sobre las recetas
(source_id + compiler/target/link + patches + flags + fases + hashes de deps recursivos) SIN bajar
fuentes ni compilar. artifact_hash() ya era pub; el CLI sólo lo expone. Cero cambios en la lógica
de hashing ⇒ NINGÚN sellado se mueve.
Verificado: `hash` da EXACTAMENTE el mismo hash que `build` (samurai, byte a byte); `--check` sobre
receta editada → NO-SELLADO en 2ms, exit 1, sin construir nada.
static-audit.sh ahora selecciona el artefacto VIGENTE por hash, no el más reciente por mtime. Con
fallback a ls -dt si hammer no está compilado. Efecto en el store completo: las ~65 recetas cuyo
sellado no es el vigente pasan de 'auditadas' (falsa cobertura) a 'sin artefacto' (deuda de rebuild
REAL, ahora visible): estáticas de verdad 615 | MIENTEN 0 | sin artefacto 123. Corre en 15s, sin
build. El '58 sin medir' de antes estaba enmascarando ~65 recetas más.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mi hipótesis de partida era falsa y conviene dejarlo escrito: el libgcc_s.so.1 NO lo metía un
sys-crate en C, y -static-libgcc (el patrón de cmake) no aplicaba — ese patrón es para C/C++.
CAUSA REAL, una sola y compartida: ambas recetas PISABAN la fase compile del lab. El lab sólo
autogenera la fase si la receta no la trae (lib.rs:779, `if out.compile.is_none()`). Las dos venían
del import de Alpine con un `cargo build --frozen --release` propio que REEMPLAZA el comando del
lab entero, y con él las tres cosas que hacen honesto al link=static:
-C target-feature=+crt-static -C relocation-model=static -C linker=.hammer-zig-cc
Sin crt-static, el rust enlaza dinámico contra musl Y contra libgcc_s — que es el UNWINDER DE LA
STD DE RUSTC, no un sys-crate.
LA EVIDENCIA QUE LO CIERRA (contraste, sin bisectar): estaba ya escrita en recipes/cargo-hack.toml
—'receta MÍNIMA sin fase compile custom (una fase cargo build produce binario DINÁMICO; el PATH
DEFAULT del lab aplica crt-static + linker zig → estático)'. cargo-hack es Rust puro y sale
estático; tuc TAMBIÉN es Rust puro, sin una línea de C, y arrastraba el mismo libgcc_s. Misma
toolchain, único delta = quién arma el cargo.
tuc: borrada la fase compile (no aportaba nada sobre el default) + flags=["--bin","tuc"] (cargo
rustc exige un único target y tuc expone lib+bin homónimos). NEEDED=0, tuc 1.3.0 corre y
'hola,mundo | tuc -d, -f2' → mundo.
helix: la fase custom es OBLIGATORIA (exporta HELIX_DEFAULT_RUNTIME y HELIX_DISABLE_AUTO_GRAMMAR_
BUILD) ⇒ replicado a mano el setup del lab siguiendo cargo-edit. Su raíz es un manifiesto
VIRTUAL ⇒ 'cargo rustc' cortaba con 'is a virtual manifest'; resuelto con -p helix-term, que
declara [[bin]] name="hx". Sigue con compiler=gcc (el gueto cc-rs/tree-sitter es real).
NEEDED=0, helix 25.07.1 corre.
MARCADOR: static-audit global = estáticos de verdad: 680 | MIENTEN: 0. Con dos honestidades: el
audit corrió DESPUÉS de los rebuilds (si no, sobre-reporta), y quedan 58 'sin artefacto o sin ELF'
que NO son un pase sino recetas no medidas. El cero es real para lo que el store cubre hoy, no una
prueba de clausura.
TRAMPA ARMADA (no tocada): el riesgo generaliza a toda receta Cargo importada de Alpine que traiga
compile propio — alpine_import.rs traduce el build() del APKBUILD literal. Hoy las 5 con compile
propio llevan crt-static, pero cada receta Rust nueva puede nacer mintiendo. Un gate barato: que el
lab avise/falle si una receta Cargo con link=static define compile sin crt-static. Cambia el lab ⇒
re-hashea sellados ⇒ decisión aparte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
De las 4, sólo 2 eran mentiras reales. Las otras 2 eran ARTEFACTOS RANCIOS: el audit hace
`ls -dt store/*-<n>` = el sellado MÁS RECIENTE, que no es el VIGENTE (hammer no expone el hash de
una receta sin construirla — no hay 'hammer hash' ni dry-run). Con el store desactualizado, el
audit acusa a recetas ya sanas. El propio script lo advierte ('correr DESPUÉS del rebuild, nunca
antes') y aun así la lista del frente se armó sin re-sellar. => el '11 mienten' está INFLADO;
hay que re-sellar antes de asumir que cada una necesita fix.
pcre2 — mentira real de libtool. NEEDED libz.so.1+libc.so → 0. -all-static en compile Y install.
pcre2grep 10.47 corre y matchea de verdad ('foo bar' con 'b(a|o)r').
libcap — mentira real, pero la causa NO era libtool: Makefile plano, -all-static no aplica.
progs/Makefile ASIGNA `LDFLAGS = -Wl,-Bstatic` + sufijo `-Wl,-Bdynamic`, y una asignación del
makefile PISA el LDFLAGS=-static del lab. El comentario de upstream admite que su default es
'estático contra libcap.a, DINÁMICO contra libc'. Fix = LIBCSTATIC=yes, la palanca que upstream
expone justo para esto (la usa su kdebug/test-kernel.sh) ⇒ rama con `LDFLAGS = --static` sin
sufijo. NO se pasa LDFLAGS por línea de comando: pisaría la rama y devolvería el sufijo.
NEEDED=0; capsh/getcap/setcap corren y getpcaps devuelve caps reales.
dbus — no mentía. Construye con MESON, no libtool ⇒ el agujero no existe acá. Ya tenía
--prefer-static -Ddefault_library=static: libdbus-1.a, cero .so, todos los binarios NEEDED=0.
Verificado end-to-end: dbus-run-session levantó un daemon estático y dbus-send ListNames obtuvo
method return. Sólo se documentó.
libnl — no mentía. Ya tenía --disable-shared --enable-static --disable-cli: 0 .so, 6 .a, sin
ejecutables (es el 'sin ELF: 1' del audit, esperado). libnl-3.a válido con nl_connect/
nl_socket_alloc como T. Sólo se documentó.
LECCIÓN: -all-static no es la única causa. Hay paquetes que pisan LDFLAGS por asignación de
makefile (libcap) y otros que ya están bien. Hay que LEER el Makefile, no aplicar el patrón a ciegas.
Van 9 de 11 (2 eran falsos positivos).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
naabu declaraba link=static y salía dinámica con NEEDED: libdl.so.2, libpthread.so.0, libc.so.6.
Ese libc.so.6 parecía GLIBC dentro de una distro musl — un cuerpo extraño. NO lo es.
CAUSA, y es la inversión contraintuitiva: CGO_ENABLED=0 es quien lo CAUSA, no quien lo evita.
naabu → Mzack9999/gopacket → ebitengine/purego v0.10.0, una FFI SIN cgo que dlopenea libpcap en
runtime. Su internal/fakecgo/zsymbols_linux.go está guardado con //go:build !cgo y hardcodea:
//go:cgo_import_dynamic purego_malloc malloc "libc.so.6"
//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0"
//go:cgo_import_dynamic purego_dlopen dlopen "libdl.so.2"
El linker interno de Go honra esos pragmas ⇒ emite binario dinámico (derrotando link=static) con
esos DT_NEEDED literales. Verificado en el artefacto: CGO_ENABLED=0 y purego v0.10.0 presentes.
NO hay glibc: el rootfs Alpine no tiene NI UN libc.so.6, y el INTERP es musl (se construyó en el
sandbox). La musl los absorbe — su ldso reserva esos nombres y los resuelve a sí misma. Strings
vestigiales, no dependencia real. Corre en musl puro (verificado con bwrap) y escanea de verdad.
OJO: en el laptop engaña — este host tiene glibc Y musl.
FIX = declarar la verdad. link=static es INALCANZABLE por diseño: purego existe para dlopen, y un
estático no puede dlopen. cgo=true tampoco sirve: el lab liga con -extldflags=-static ⇒ mismo muro,
y lobotomizaría el escaneo SYN en silencio.
DATO DURO: el binario nuevo es BYTE-IDÉNTICO al viejo ⇒ link=static nunca hizo NADA en recetas Go
(go build no lee LDFLAGS con CGO off): era pura declaración falsa. Las demás recetas Go pasan el
audit por accidente — sin purego, CGO_ENABLED=0 da estático natural.
TICKET APARTE (no tocado): naabu no declara libpcap y NO existe recipes/libpcap.toml. El escaneo
SYN —su feature principal— está INERTE hasta que exista. No es expresable en [deps] hoy: no se
linkea, se dlopenea.
Van 5 de 11.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>