645 líneas. Los ADR entran porque en este repo SON documentos vivos, no
registros inmutables: el 0013 tiene 5 commits, el 0009 dos. Eso se comprobó
antes de decidir, no se asumió por convención general.
EXCLUIDOS por ser REGISTRO o generado: docs/evidencia/ (6), el HANDOFF de la
noche de KDE (1) y docs/state/ (24, se regenera solo). Reescribir un comando
dentro de una evidencia la falsifica.
Y el ADR 0016 se excluye de todo barrido, con un aviso adentro para el próximo
que barra: habla SOBRE el renombre, así que necesita seguir diciendo 'hammer'.
El barrido se lo llevó puesto y lo dejó titulado 'Renombre del sistema: takana
→ takana'; revertido.
Congelados, verificados uno por uno con controles: /opt/hammer, /var/lib/hammer,
/usr/bin/hammer, /mnt/vvv/hammer, la URL de gitea, hammer-farm.service,
hammer-live-install.sh, BRIEFING-hammer.md, hammerd, hammer-recover y
HAMMER_LIVE.
El documento decía que `lsof` y `tzdata` «siguen vetando a propósito», y ya no: las 26 recetas que
faltaban están hechas desde su fuente pineada. Se agrega la sección del cierre, con el fallo del
guardián que apareció al medir (resolvía por nombre de FICHERO y el paquete se llama por su campo
`name`: 14 falsos vetos) y los dos paquetes que NO se pueden redistribuir, que ahora el veto ve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
D8 decía que sniper «entra al store por `file_drop`». Dos correcciones, y la
primera es de vocabulario: **`file_drop` en hammer es otra cosa** — una
operación de `hammer apply` que coloca un fichero en el sistema instalado
verificando su hash. No tenía nada que ver con sellar. Lo que sella es lo de
siempre, una receta. Queda escrito en el ADR: un término inventado que suena a
mecanismo existente manda a buscar el código donde no está.
`recipes/steam-runtime-sniper.toml` sella el árbol del runtime (11196 ficheros)
pineado por el sha256 que ya estaba verificado. Entra donde Arch y Ubuntu no
pueden por una propiedad, no por simpatía: **no muta** —nadie le instala nada
adentro— así que el mismo tarball da siempre el mismo árbol y sellarlo es una
afirmación verdadera.
**La marca: `foreign = true`.** No cambia el build en un byte y **no entra en
`hash_inputs`** (describe procedencia, no identidad — hay test). Lo que cambia
es contable: `build-state.py` la clasifica `ajeno`, la resta del denominador de
las imágenes y la deja fuera del recuento de recetas. Sin eso, sellar un
prebuilt habría subido la cifra que todo el mundo lee como «cuánto
construimos» — el riesgo que el ADR escribió antes de que existiera la primera
instancia. Verificado: sigue diciendo 821 recetas, y aparte
`de las ajenas, 1 selladas al store (prebuilt pineado, sin procedencia de fuente)`.
Y la diferencia con el otro ajeno: `xwayland` no se hashea (no hay receta, y un
hash afirmaría que lo reproducimos); el sellado **sí conserva su hash**, porque
está en el store y que un artefacto exista mientras el grafo lo niega sería otra
forma de mentir. Comparten el estado, que es lo que protege la cifra.
**`hammer qorpa import --from-store <hash>`** lo consume, y ahí está el detalle
que hace que valga: la imagen se registra bajo el **sha256 del archivo de
upstream**, no bajo el ArtifactHash. Al revés, la imagen del store y la traída
con `pull` serían dos imágenes distintas con los mismos bytes y las instancias
de dos máquinas dejarían de coincidir — justo lo que el pin existe para evitar.
El árbol se **enlaza**: una imagen nunca se escribe (lo que escribe la instancia
va a su `upper`), así que compartir inodos con un artefacto sellado y de sólo
lectura es correcto por construcción y la imagen cuesta ~0 bytes. La contracara
conocida de `.dmerge`: mientras el artefacto siga en el store, borrar la imagen
no libera disco; `--copy` lo evita.
Licencia `LicenseRef-qorpa-ajena-no-enumerable` a propósito: adentro hay cientos
de paquetes Debian y no podemos enumerarlos; vacío se leería como «todavía no la
poblamos». SDD 20 lo recoge y afila la distinción: replicarla a nuestras
máquinas es lo que ya hace ADR 0013 con las fuentes; publicarla a terceros sigue
pidiendo licencia y marca.
29 tests verdes. El sellado en sí corre aparte, esperando el lock de la granja.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QZjGRqvjWij9dew7NVFu4Q
Paso 7 del ADR 0015, sus dos mitades.
PODA. Un rootfs son cientos de MB o varios GB y no lo alcanzan ni store-gc ni
la caché .dmerge: es un tercer montón sin dueño, como ya lo fue work/sources.
`hammer qorpa prune` barre restos de pulls a medias, imágenes que ninguna
instancia usa —se re-traen por digest, que es justo la propiedad que da el pin—
y, con --upper, las capas mutables, que por D3 siempre se pueden tirar.
Con las dos cicatrices del repo cableadas:
- SIN --yes es un simulacro. Y tras borrar COMPRUEBA que el directorio se fue,
porque store-gc reportaba borrados que no ocurrían y eso se descubrió tarde.
Si dice que sí y sigue ahí, sale ≠0 diciendo que el número de arriba no es lo
que se liberó.
- El tamaño de un árbol con directorios ilegibles sale MENOR de lo que es: un
upper con ficheros de los subuid no se puede recorrer entero. Ahora se cuentan
los directorios ciegos y la cifra se marca con `≥`. Un número silenciosamente
bajo es peor que ninguno cuando con él se decide borrar.
Y --upper deja la instancia USABLE: sin upper/ y work/ no vuelve a arrancar.
LICENCIAS (SDD 20). Las imágenes ajenas quedan fuera del catálogo publicable y
del reporte de licencias por escrito, y la razón no es pereza: no podemos
enumerarlas — un `pacman -S` dentro de una instancia trae paquetes que nadie
declaró acá, y afirmar una licencia sobre eso sería inventarla. Lo que sí se
hace: contarlas aparte en clase `ajeno` (el riesgo real del ADR es que en seis
meses alguien las cuente como corpus), y dejar dicho que si algún día se espejan
hay que mirar licencia Y MARCA antes, igual que con Firefox. Más el corolario
que faltaba escribir donde se lea: el claim «hammer reproduce bit a bit» hay que
acotarlo desde el día que exista una instancia.
2 tests nuevos (la poda no toca una imagen en uso y sí los restos; con --upper
la capa se va pero la instancia queda usable). 50/50.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2
Diagnóstico completo. No es daño de la campaña del split ni un fallo de store-gc.
LO MEDIDO, en orden:
1. 86 nodos del perfil sin artefacto en su hash vigente.
2. NO lo causó el split: revertidas una por una todas las recetas tocadas hoy (3 bibliotecas
base, 30 hojas, dbus, 38 de la 2ª tanda) el número NO se movió de 198 en ningún caso.
3. La FRONTERA son 7 recetas —las únicas cuyas deps sí están al día—: dbus, libnl, libpcap,
libxkbcommon, lm-sensors, perl-xml-parser, vulkan-loader. Las otras 79 cuelgan de ellas.
4. Las 7 son SOMBRAS de incoming-kde, no las canónicas. Las del corpus están al día, y por eso
el problema es INVISIBLE desde el grafo del corpus — que es justo el que yo miraba.
5. No fue store-gc: sus hashes vigentes no figuran en ningún manifiesto de borrado, y tres nunca
se podaron. (Lo sospeché porque la regla de «superado» es por NOMBRE y una sombra comparte
nombre con la canónica; la sospecha era razonable y los manifiestos la descartan.)
6. Ni el hub ni el worker los tienen. No están en otro sitio: NO ESTÁN.
LA CAUSA DE FONDO, que importa más que KDE: la cola se construyó en workers EFÍMEROS, el sync
del store es unidireccional (worker→hub) y esos workers ya no existen. Cuando algo del sustrato
compartido cambió después, las sombras se re-hashearon y nadie las reconstruyó, porque el hub
nunca fue la máquina donde vivía ese escritorio.
⇒ Con flota efímera y sync unidireccional, un escritorio puede dejar de ser
reconstruible-desde-el-store SIN QUE NINGÚN INDICADOR LO DIGA. El grafo seguía reportando
162/162 desde un JSON generado semanas antes.
Coste de arreglarlo: 86 reconstrucciones en la granja, empezando por las 7 de la frontera. No
hay muro técnico conocido — son recetas que ya construyeron. Es cómputo, no investigación.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Al desplegar la 2ª tanda de hojas noté 198 recetas de las colas de escritorio sin artefacto
vigente (KDE 133, GNOME 37, COSMIC 24) y asumí que las había roto yo. **No.**
LO VERIFIQUÉ REVIRTIENDO, una causa por vez: las 3 bibliotecas base (expat, zstd, ncurses), las
30 hojas de la 1ª tanda, `dbus`, y las 38 de la 2ª. **El número no se movió de 198 en ningún
caso.** Y mirando el histórico, `escritorio-kde` está en 76/162 en TODOS los commits recientes
—09:10, 09:41, 10:12, 10:44, 11:16, 11:49— o sea desde antes de que la campaña empezara.
EL ERROR FUE MÍO Y DE MÉTODO: escribí «KDE cierra 162/162» leyendo
`docs/state/build-state-kde.json` **sin regenerarlo**. Es un fichero GENERADO, y un generado que
no se regenera es una foto vieja con aspecto de dato fresco. El mismo tipo de fallo que citar el
«79%» midiendo otra unidad: el dato existía, lo que fallaba era de cuándo era.
⇒ Y hay un aviso de fondo para toda la sesión: **medí «cero daño colateral» sobre el grafo
`--wlr`, que sólo carga corpus + incoming-wlr.** Las colas de escritorio son INVISIBLES ahí, así
que ese «cero» era cierto en el grafo que miraba y no decía nada del catálogo completo. Para
juzgar impacto hay que cargar TODAS las colas.
Las 38 de la 2ª tanda quedan revertidas: hasta entender los 198 no conviene añadir cambios
encima. Las 30 de la 1ª y las 5 del piloto siguen puestas y verificadas.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El «79%» que este plan y los SDD 19/20 venían citando era **el 79% del CONTENIDO BINARIO**,
medido sobre una muestra de .so/.a. Es cierto y es la cifra equivocada para planificar, porque un
artefacto no es sólo binarios: trae cabeceras, datos, iconos, locales, .pc y documentación, que
no encogen.
MEDIDO con `scripts/medir-debug.sh` (suma los tamaños reales de las secciones .debug_* vía
readelf -S y los compara con el tamaño del árbol, sin reconstruir nada):
40 artefactos · 876 MB · 27%
100 artefactos · 2955 MB · 38% (truncada a 60 ficheros/artefacto)
250 artefactos · 8874 MB · 60% ← la que manda: sin truncar, ~7% del store
La varianza es alta porque unos pocos artefactos grandes dominan el total, así que el número
necesitaba validación independiente — y la tiene: el piloto de la etapa 2, donde se reconstruyó y
se pesó de verdad, dio bison −50% y appstream −68%. Los dos ENCIERRAN el 60% de la muestra
grande. El modelo predice lo que el rebuild produjo.
CIFRAS CORREGIDAS, con el store en 127 G:
se venía diciendo medido
reserva de disco ~96 G ~76 G
store tras el split ~30 G ~51 G
espejo público ~30 G ~51 G
Sigue siendo el mayor ahorro disponible en el proyecto —76 G no es poco— pero no es lo
prometido, y la diferencia importa para dimensionar el alojamiento del espejo, que es una
decisión con factura.
LA LECCIÓN: una cifra medida sobre una cosa (contenido binario) se citó durante semanas como si
midiera otra (tamaño de artefacto), y llegó a tres documentos. El número no era falso; LA UNIDAD
sí. Cuando un número vaya a decidir un gasto, hay que volver a la medición original y comprobar
QUÉ estaba midiendo.
Corregido en los tres sitios donde se había propagado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
§II.1 decía «medido: no hay ninguno — de toda la familia sólo existen foot y mako». Ya no: 10
recetas selladas, 12 binarios, el perfil escritorio-sway cerrando 121/121 y ARRANCANDO en QEMU
con evidencia. La hipótesis que traía la sección —«son baratos, no hay torre de C debajo»— se
cumplió: KDE y GNOME fueron campañas de semanas, esto salió en una noche.
Y se añade la lección que corrige cómo hay que leer TODO el documento: el grafo prueba que una
imagen SE ARMA, no que ARRANQUE. Entre 121/121 y una pantalla con algo dibujado hubo SEIS
muros y ocho ciclos de imagen —PATH vacío, libz.so.1 ausente del rootfs, un backend de libseat
que nuestra receta no construye, /run de sólo lectura, los datos XKB, y la falta de
tipografías— y ninguno era una dependencia de build, así que ninguno podía salir en el grafo.
Con el método incluido, porque es lo que más se olvida: el veredicto es CONTAR COLORES del
PPM, no leer el log. Hubo un arranque con todo verde (salida activada, modo correcto, «Commit
of 1 outputs succeeded», workspace creado) y la captura 100% negra.
⇒ Corolario escrito en una sección nueva: cuando este documento diga que algo «cierra», hay que
leerlo como «se puede intentar», no como «funciona». Los tres escritorios que aún no se
validaron con pantalla deben esa misma distancia, y no conviene prometer fechas contra el
número del grafo.
waybar queda anotado como fuera-por-medición (gtkmm-3.0 = GTK3 + bindings C++ + 8 recetas), con
yambar en su lugar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El catálogo decía que el texto de la licencia se inyecta en `hammer pack`. El razonamiento
del coste era correcto pero el sitio estaba mal: pack emite un .swm (receta sobre fuente
pública, sin binarios) e install reproduce construyendo, así que el canal de paquetes no
distribuye binarios. El punto de entrega es la IMAGEN, y ahí está hecho, con veto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fui a destrabar «las 12 recetas en deuda» con el diagnóstico heredado (fallan por el rootfs
del worker: python OSError en meson, find_package en cmake). Lo probé construyéndolas de
verdad y **el diagnóstico es falso**: cmake y meson corren perfectamente ahí. Son cuatro
situaciones distintas y ninguna es el rootfs.
(a) SEIS ESTÁN SUPERADAS, NO EN DEUDA. `gtk4` del corpus es link=static y enlaza
libfontconfig.a, que tiene 1122 reubicaciones no-PIC ⇒ 13.032 errores
`R_X86_64_64 cannot be used against local symbol`. No es una receta rota: es imposible.
Y mientras tanto recipes/incoming-gnome/gtk4.toml (link=dynamic, deps -shared) YA ESTÁ
SELLADA, con libadwaita y fontconfig-shared. O sea que la cadena estática del corpus
—gtk4, libadwaita, gtksourceview y los tres hello/edit que cuelgan— es un DUPLICADO
superado de la cadena dinámica de GNOME.
Cerrarla no es construirla: es decidir si se promueven las sombras -shared al corpus,
porque la resolución de deps es hermano→padre. Es una decisión de arquitectura, y hay
aviso registrado de que promover a ciegas hace que variantes homónimas pisen recetas
canónicas. NO la tomo yo.
(b) wlr-randr: cerrada en el commit anterior.
(c) dwarves: frente propio con muro identificado, no deuda. Nuestro elfutils entrega SÓLO
libelf a propósito (libdw arrastra argp/obstack/fts, lo difícil en musl). No se arregla
ampliando elfutils: de él cuelga el kernel que ya reproduce bit a bit. El camino es una
receta aparte `elfutils-libdw`, con el patrón de las sombras -shared. Sin urgencia:
ningún perfil pide dwarves y el kernel desactiva DEBUG_INFO_BTF justamente por su
ausencia. Queda escrito en la cabecera de la receta, que es donde se va a leer.
(d) mirada-compositor, mirada-greeter, llimphi-counter: source por SSH a
git.tawasuyu.net y el worker es SIN SECRETOS por diseño ⇒ HUB-ONLY estructural, no
fallo. llimphi-counter ni siquiera llega a construir: su commit son ceros con el
comentario «fijar al commit real». Receta sin terminar, y es de tawasuyu.
EL HALLAZGO DE FONDO: el bucle del worker sólo recorre las colas incoming-*; el corpus
(recipes/) NO está en QUEUES. Las 12 nunca se habían intentado allí. Buena parte de «la
deuda» era de ENCOLADO, no técnica — y por eso el diagnóstico heredado nunca se verificó.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pedido del usuario: «el catálogo de todo lo que falta hasta decir la distro está
publicable, y lo de más hasta la distro está completa. Y un tipo manual, todos los pasos
que tengo que ir haciendo en las tres etapas que marcan esos dos codos.»
SDD 20 = el inventario, medido contra el repo (grafo, no recuento a mano). SDD 21 = el
manual de a pie, un comando por vez, marcando qué puede hacer el agente y qué es del
usuario.
LO QUE APARECIÓ AL MEDIR, y que no se sabía:
· La deuda de construcción NO son 12+12+13+12 recetas: es UNA lista de 12 vista desde
cuatro grafos (la cadena GUI de la cascada de mesa). Un solo arreglo —declarar python3 y
cmake en `[deps]`, no engordar el rootfs del worker— las destraba todas. `base` y `cli`
ya están al 100%, KDE cierra 162/162.
· De WMs Wayland ligeros no hay NINGUNO: sólo `foot` y `mako`. Falta wlroots entero, los
compositores y todos los accesorios (waybar, fuzzel, swaybg, grim, slurp, wl-clipboard).
· De aplicaciones gráficas de terceros hay CERO. Ni visor de imágenes ni lector de PDF.
· Firefox no es «una receta más»: su cadena de `*-sys` con C++ es justo la frontera que el
techo MSRV del sandbox (1.96) no cruza. Antes de Firefox hay que subir el techo; empezar
por la receta es empezar por el final.
· El texto de la licencia va inyectado en `hammer pack`, NO en la fase install: las fases
entran al ArtifactHash, así que hacerlo en la receta re-hashearía las 1141. Pack es aguas
abajo y sale gratis. Misma lógica que hizo pagable el campo `license`.
Dos correcciones al SDD 19, que escribí yo ayer y tenía mal:
· «5 de 771 recetas declaran licencia» — falso por partida doble. El número real era 0 de
1141; los «5» eran falsos positivos de `grep license` (nombres de paquete, una línea de
install, un comentario) y 771 son los nodos del grafo del corpus, no las recetas.
· «El repo no declara licencia» — falso: hay LICENSE (MIT) en la raíz y en Cargo.toml. No
lo miré antes de escribirlo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>