`recipes/incoming/.deferred/` decia "24 recetas aparcadas con diagnostico, p.ej.
git con muro en libgit.a". Las dos mitades de esa frase eran falsas.
Cinco de las 24 ya estaban RESUELTAS en otro fichero y sellan hoy: dprint, git,
gmp, mise e yj (recipes/git.toml, recipes/mise.toml, incoming-kde/gmp.toml...).
La copia aparcada era la version anterior y seguia leyendose como deuda abierta
— y el ejemplo que daba el comentario era justamente uno de esos cinco. mise se
construyo esta misma tarde en el worker. Se van, con `gcc15.patch` que solo
citaba la gmp aparcada (ninguna de las dos gmp que sellan declara patches).
Y las 19 que quedan no estan "aparcadas con diagnostico": son volcado CRUDO del
importador, 15 de import-nix y 4 de import-alpine, todas con la cabecera "PUNTO
DE PARTIDA, no final". Nadie las intento todavia; no hay ningun muro que
respetar. Es una cola de trabajo sin empezar, no una lista de imposibles, y
leerla como lo segundo desanima de tocarla.
Gate --check OK, grafo CIERRA, corpus 786/787; las 5 que sellan intactas.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
Era la cola de staging de las tandas base-system/foot y sus recetas ya se
habian PROMOVIDO a canonicas commit a commit ("promover 7 tier-2", "promover
cadena crypto", "promover tllist/utf8proc/scdoc/fcft/foot"). Lo que quedaba
eran 0 .toml y 26 .patch: 5 copias byte a byte de la version canonica de
recipes/ y 21 de recetas que ya no viven ahi.
No eran "probablemente inutiles": son inalcanzables POR CONSTRUCCION.
`fetch::apply_patches` resuelve `recipe.base_dir.join(nombre)` y `base_dir` es
el directorio de la receta, sin fallback a `recipes/` — un parche en una cola
sin recetas no lo puede pedir nadie.
⚠ El corolario queda escrito en farm-worker-loop.sh: al promover una cola, los
`.patch` NO viajan con la receta, y la cola queda en un estado que PARECE vivo
(tiene ficheros) sin moler nada.
Sale de QUEUES en farm-worker-loop.sh y de la lista de promote de farm-down.sh.
`incoming-go` se queda en las dos aunque el directorio no exista: es la cola
aislada del frente Go y el guard `ls $Q/*.toml` salta las vacias; quitarla seria
dejarla muerta el dia que ese frente la recree.
Verificado con la regla ESTRICTA de resolucion de parches sobre todo el corpus:
94 referencias resuelven, 0 rotas. (El chequeo que use al jubilar onda-2 era mas
laxo que hammer — aceptaba recipes/<parche> como fallback; su conclusion se
sostiene, pero el metodo queda corregido.)
Gate --check OK, grafo CIERRA, corpus 786/787.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
El caso LIMPIO del mismo fenomeno que onda-1, sin ninguna de sus asperezas: las
22 recetas son byte a byte identicas a las de incoming-gnome Y las 22 dan el
MISMO ArtifactHash, o sea la misma direccion del store. Sin divergencias, sin
colision de nombre y sin un solo artefacto que podar al retirarla.
La comprobacion que decide NO es `diff` sino `hammer hash`: la resolucion de
deps es hermano->padre, asi que dos ficheros identicos en colas distintas pueden
sellar distinto. Aca coinciden los 22.
Se va tambien `cairo-ctime-r.patch`, verificado identico al de incoming-gnome, y
comprobado que ninguna receta de ninguna cola queda apuntando a un parche
inexistente.
El andamio ya no sostenia nada: el trabajo de la isla dinamica vive en
incoming-gnome, que es la cola que el perfil usa. Sale de QUEUES en el mismo
commit, por la simetrica de la regla que ese fichero documenta.
Gate --check OK, grafo CIERRA, escritorio-gnome sigue 119/119, corpus 786/787.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
Cierra la jubilacion empezada con gnome-desktop. Las 5 recetas que quedaban:
- glib, glib-introspected, gobject-introspection, py3-setuptools: byte a byte
identicas a las de incoming-gnome Y con el MISMO ArtifactHash, o sea que
sellaban en la misma direccion y entraban por cache-hit. Coste de build cero;
coste real: un segundo sitio donde editar, que se separa en silencio.
- gsettings-desktop-schemas: la unica distinta, y es la version ANTERIOR al
2026-07-28 (introspection=false + link=static). La vigente lo activo porque el
gir de Meta de mutter incluye GDesktopEnums-3.0.gir, que sale de ahi, y sin el
el g-ir-scanner corta en el ultimo target de mutter (721/722).
Lo caro no era el tiempo sino el NOMBRE: el gsettings viejo competia con el
bueno, dos artefactos homonimos con hash distinto en el store. Podado el
huerfano (0fb1703d, 155 KiB exclusivos) y anotado en el ledger anti-churn; los
otros cuatro artefactos siguen vigentes porque los produce incoming-gnome.
Y al jubilar gnome-desktop las dos hojas de la cola se habian quedado sin
consumidor: la cola se alimentaba a si misma y terminaba en el aire.
Sale tambien de QUEUES en farm-worker-loop.sh, por la simetrica de la regla que
ese fichero ya documenta dos veces: una cola ausente de la lista no da error,
da silencio — y una cola retirada que sigue en la lista, tambien.
Gate --check OK, grafo CIERRA, escritorio-gnome sigue 119/119, corpus 786/787.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
Es el mismo fósil que el propio script documenta para GNOME, repetido con las colas
que nacieron después. Las 25 recetas en deuda de COSMIC no fallaban: nadie las
intentaba, porque su cola no estaba en la lista. Una cola ausente no da error, da
silencio — el mismo modo de fallo que "el corpus no está en las QUEUES del worker".
Se añade la regla en el comentario: cola nueva y línea QUEUES, en el mismo commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
El default era fósil de cuando `incoming-gnome-onda1` era el frente entero:
listaba esa cola (6 recetas) pero NO `incoming-gnome` (86, donde viven gtk4,
mutter y gnome-shell) ni `incoming-gnome-onda2` (la isla dinámica, 22). El
worker reportaba moler GNOME y molía 6 de 114.
Se agregan las dos y se mueven las tres ANTES de incoming-kde: con 205 recetas
KDE por delante, un ciclo se consumía sin darles turno aunque estuvieran
listadas.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Las 3 raíces de la onda 1 (gsettings-desktop-schemas, gobject-introspection,
gnome-desktop) copiadas a una cola propia + añadida al QUEUES del worker-loop.
Aislar evita que el worker dispare rebuilds de spidermonkey/mutter (onda 2/3).
El cierre real de RECETAS de las 3 es 39 nodos, 0 en incoming-kde: el "gap del
resolver" que temía era de seed-edges (.pc cairo→libX11), NO de deps declaradas.
El espinazo corpus (38 nodos: gtk4/cairo/pango/gdk-pixbuf en recipes/) está 100%
sellado ⇒ el worker cachea todo e sólo construye las 3. Hashes byte-idénticos a
los sellos de incoming-gnome (base_dir no entra al hash).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`build-farm.sh` usa `xargs -P$JOBS`; con >1, dos recetas que comparten dep disparan la carrera del
árbol de fuentes del ADR 0012 y el árbol queda roto para siempre. Se vio a escala: al invalidar
libdrm, 118 de las 205 recetas KDE pasaron de cache-hit a rebuild real y ~93 murieron con
"/src/.zwrap/cc is not a full path to an existing compiler tool" — el wrapper de zig que la receta
deja en el ÁRBOL DE FUENTE, barrido por el fetch concurrente de otra receta sobre el mismo qtbase.
El reintento serial de build-farm tampoco las recupera: el árbol ya quedó inconsistente.
La caché estaba ocultando la carrera, no evitándola. Serializar es la única mitigación correcta
hasta que el ADR 0012 elija salida.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El `echo "ciclo: N recetas en $Q"` sale ANTES de pedir el lock, así que con la campaña corriendo el
journal mostraba el anuncio del ciclo y después nada: parece un loop trabajando y es un loop
bloqueado. Es la misma clase de problema que el "no encuentro el ejecutable zig" apuntando al
directorio equivocado — un log que miente cuesta horas de diagnóstico.
`flock -n` primero, y sólo si falla se anuncia la espera y se bloquea. Sin coste cuando el lock
está libre, que es el caso normal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Los dos construyen sobre el MISMO work/, y `fetch` nombra el árbol de fuentes de forma determinista
(`work/sources/<receta>-<sha16>`, sin nada que dependa de QUIÉN construye) ⇒ dos procesos que
necesiten la misma DEP apuntan al mismo directorio: uno hace `remove_dir_all` mientras el otro
corre `tar -x`. El árbol queda a medias y devuelve "Directory not empty" (os error 39), y así se
queda hasta que alguien lo borra a mano.
No es teórico: la campaña de deuda y el loop pidieron `mesa` a la vez (el loop lo arrastraba desde
la cola KDE al invalidarse libdrm) y se llevó puesta media cascada GUI, con un error que no nombra
la causa. Casi borro ese árbol a mano antes de ver que había un bwrap montándolo.
Grano: la campaña toma el lock para toda su corrida; el loop, por ciclo de cola. Más fino rompería
el `xargs -P2` de build-farm.sh.
Dos honestidades en los comentarios, para no prometer de más:
- `flock` NO es FIFO. El loop vuelve a pedirlo enseguida y le gana a la campaña que espera:
medido, la campaña entra al terminar TODAS las colas, no entre dos. La ventana real es el
IDLE_SLEEP. Por eso espera con techo (LOCK_WAIT=7200) y sale limpia en vez de colgarse.
- Esto NO cierra la carrera del todo: el `-P2` interno del loop puede correr dos recetas de la
misma cola que compartan dep, y ésas se siguen pisando. El arreglo de fondo es un lock POR
ÁRBOL dentro de `fetch`, que cubriría los dos casos.
Verificado con flock real: exclusión mutua (la campaña entra justo cuando el loop suelta), salida
limpia por timeout con rc=0, y la no-equidad de flock medida en vez de supuesta.
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>
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>
recipes/incoming/ es la cola del stack gráfico tawasuyu, de otro agente. Sus 7 recetas son
imports crudos de Alpine con sha256='FIXME-sha256' y deps sin expandir (clang$_llvmver, _dev,
py3-gpep517) ⇒ NO construibles por construcción. El worker las fallaba en CADA vuelta (CPU
pagada) y farm-down repetía los 6 errores al bajar, con pinta de ser nuestros.
Su trabajo YA aterrizó por la vía canónica (7131cd4 'MESA iris-only CONSTRUIDA — stack gráfico
COMPLETO'), así que la cola quedó obsoleta. Pero NO es nuestra para borrarla: 370e7b7 ya la
parqueó una vez como cruft y 5126a8b tuvo que revertirlo. Dejamos sus ficheros en paz y sólo
los sacamos de QUEUES.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El watchdog de disco purgaba work/sources/* protegiendo solo árboles bind-montados
por bwrap (prot.1) o cuyo nombre coincide con la receta en vuelo (prot.2). Una dep
transitiva (cairo bajo pango/gtk4) se extrae host-side ANTES del bwrap y con nombre
que no coincide con la receta ⇒ el rm -rf competía con el tar y dejaba el árbol a
medias: 'Cannot mkdir' durante la extracción, luego 'Directory not empty' para
siempre (falla rápido antes de bwrap, nunca se recupera). Prot.3: no purgar árboles
modificados hace <2 min (extrayéndose ahora); los fríos se purgan y re-extraen limpio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- farm-worker-loop.sh: construye QUEUES='recipes/incoming recipes/incoming-go' (en serie por vuelta;
la cola Go aislada se muele 24/7 sin pisar el incoming/ del otro agente).
- scripts/farm/harvest-go.sh: cosecha determinista sin IA (cron del laptop). Baja el store sellado,
y por cada receta SELLADA (cache-hit bajo timeout, NO compila en el hub) hace SMOKE-TEST del binario
(existe + corre version/--help sin panic/segfault) antes de promover+firmar. Lo que falla el smoke va
a tandas/needs-review-weekend/ para el lunes. add explícito (nunca -A), push con reintento.
vault (go-15) falló en 'go mod vendor' con '~/.cache/go-build/...: no such file': durante el compile
de telegraf el disco cruzó DISK_HIGH, el watchdog corrió 'go clean -cache', y el vendor de vault (que
usa esa cache) arrancó en la ventana siguiente sin ficheros. El guard anterior solo miraba 'go mod
vendor' activo en ese instante; ahora difiere la purga si hay cualquier 'hammer build' en vuelo.
go-13 (dnscontrol/lazysql, árboles de deps Go enormes) destapó otra race del watchdog: durante el
fetch/'go mod vendor'/resolve_phases (host-side, antes del bwrap) el árbol no está bind-montado ⇒ el
watchdog lo borraba a mitad del vendor → go.mod desaparecía → 'receta sin compile, heurística no
encontró build system'. Ahora además protege work/sources/<name>-* si <name> tiene un proceso
'hammer build' activo (cubre toda la vida del build, no solo la fase sandbox).
go-12 (terraform/k8s) destapó la race: con árboles de deps de varios GB en paralelo, el disco
cruza DISK_HIGH y el watchdog corría 'go clean -modcache' a mitad de los 'go mod vendor' en vuelo
(.partial: no such file) ⇒ 5 recetas no convergían. Ahora difiere la purga del modcache si hay un
vendor de host activo (la purga de work/sources ya libera lo grueso y es segura). syncthing: main
real en ./cmd/syncthing (la raíz tiene build-constraints que excluyen todo).
El 'go mod vendor' acumula GOMODCACHE (~/go/pkg/mod) sin tope: una tanda Go
grande lo llevo a 31G y lleno el disco de 80G -> I/O-wait disparo el load a 23
(cuello real, no CPU/RAM). El watchdog ahora purga 'go clean -modcache/-cache'
cuando el disco supera DISK_HIGH% (def 82). El vendor/ local de cada receta ya
tiene lo necesario; si pisa un vendor en curso, esa receta reintenta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paraleliza el build a un/varios VPS sin exponer gitea ni la clave de firma.
Modelo: el laptop es el HUB (firma + gitea); el worker sólo construye la cola
incoming/ y sella al store local (PROMOTE=0). Reproducibilidad bit-a-bit +
content-addressing ⇒ el store del worker es byte-idéntico, se rsync-ea de vuelta
y el hash valida solo (no hay que confiar en el VPS).
- vps-setup.sh: provisiona Debian/Ubuntu (deps, userns p/bwrap, swap 16G,
rustup, build hammer, bootstrap-devfs rootfs Alpine edge, systemd unit).
- farm-worker-loop.sh: loop autónomo 24/7, PROMOTE=0, watchdog de disco
integrado; muele lo que haya sin esperar al hub.
- hammer-farm.service: systemd, JOBS=2 (CCX13 = 2 vCPU dedicado), Nice/idle-io.
- farm-sync.sh (hub): rsync código+cola arriba, store sellado abajo,
promote+firma+commit+push. Acceso único laptop->VPS por SSH.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>