Commit Graph
1611 Commits
Author SHA1 Message Date
Sergio dd36ddebe2 estado: cosecha granja 2026-08-30T18:32:00Z — avance del árbol KDE 2026-08-30 18:32:00 +00:00
Sergio 30a319751b estado: cosecha granja 2026-08-30T18:01:58Z — avance del árbol KDE 2026-08-30 18:01:58 +00:00
Sergio 0e8aa5364e estado: cosecha granja 2026-08-30T17:32:05Z — avance del árbol KDE 2026-08-30 17:32:05 +00:00
Sergio 8a87b78a75 estado: cosecha granja 2026-08-30T16:32:01Z — avance del árbol KDE 2026-08-30 16:32:01 +00:00
Sergio 5d7c01d9a4 estado: cosecha granja 2026-08-30T15:01:59Z — avance del árbol KDE 2026-08-30 15:01:59 +00:00
Sergio cd919913d0 estado: cosecha granja 2026-08-30T14:32:09Z — avance del árbol KDE 2026-08-30 14:32:09 +00:00
Sergio c504450cad estado: cosecha granja 2026-08-30T12:32:04Z — avance del árbol KDE 2026-08-30 12:32:04 +00:00
Sergio 771efb2dd3 estado: cosecha granja 2026-08-30T11:32:06Z — avance del árbol KDE 2026-08-30 11:32:06 +00:00
SergioandClaude Opus 5 9142cd0565 upgrade: el mismo agujero que hydrate, en el proyector de generaciones
project_plan es target_root.join(rel) + escribir por ruta, igual que hydrate: un symlink de
directorio dejado por una generación anterior se seguía. Reproducido — la generación 2 escribía
usr/share/pkg/archivo FUERA del root y ApplyReport salía en verde.

Con --root / no cambia nada (todo empieza por /); el caso real es , que es como se arma una imagen.

Se mide el DIRECTORIO PADRE, no el destino: un Replaced sobre un symlink que apunta afuera es
legítimo porque rename pisa el symlink en vez de escribir a través de él, y medir el destino lo
rechazaría por error. Y raiz_real() resuelve el tramo existente del root dejando pegado el que
todavía no existe, para que la comprobación valga también sobre una imagen nueva.

Dos tests, uno por dirección: el que sale falla ruidoso, el que se queda dentro (usr-merge,
temas de iconos) sigue proyectando.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 11:19:10 +00:00
SergioandClaude Opus 5 726068e653 hidratación: no se escribe a través de un symlink que sale del root (SDD 25 H3)
Buscando el sitio de H3 —«harkaq valida rutas a mano»— resultó que harkaq NO valida rutas a
mano: delega en Landlock. Donde no se validaba NADA era en hydrate.

Reproducido: hidratar es target_fhs.join(rel) y escribir por ruta, así que un symlink de
directorio ya puesto en el FHS se sigue. Artefacto A trae usr/share/pkg → /algún/lado (los
symlinks se replican literales, y así debe ser); al hidratar B, usr/share/pkg/archivo aterrizaba
FUERA del root y hydrate devolvía Ok(1). Con --into sobre una imagen eso es escribir en el
anfitrión diciendo que todo fue bien.

Cuánto se estaba disparando: cero. En el store hay 160 symlinks absolutos y NINGUNO apunta a un
directorio; de ~42 000 relativos ninguno sale de su artefacto. Mina desactivada, no incendio.

La comprobación va por DIRECTORIO (un canonicalize por fichero sería un realpath por entrada,
T8) y la semántica es la de RESOLVE_IN_ROOT, no la de NO_SYMLINKS: un symlink que se queda
dentro tiene que seguir andando —usr-merge /lib → usr/lib, los 22@2x → 22 de los iconos— y hay
test para cada dirección. Lo que queda de la propuesta (la versión sin carrera, con openat2 +
linkat/renameat) queda escrito con su precio: arrastra el camino de patchelf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 11:14:13 +00:00
SergioandClaude Opus 5 19b5d51dfe latido: el contrato del kernel se comprueba solo en cada cosecha
El gate de SDD 25 §4.ter existía y NADIE lo corría. Un kernel reconstruido sin MEMCG volvería
a pasar inadvertido — y ese fallo no se ve del lado del desarrollo, porque el kernel de la
máquina de trabajo sí trae MEMCG: sólo se ve comprobando el artefacto.

Mismo cable que el vigía de fuentes y por la misma razón que está escrita ahí arriba: lo que
nadie refresca envejece hacia el optimismo. Ahora cada ciclo deja el veredicto en
docs/state/kernel-contract.txt y el git log lo muestra como el resto del khipu.

El fichero se escribe por temporal y sólo se mueve si tiene contenido: contract sale != 0
cuando el contrato no se cumple —y ese rojo es justo lo que hay que guardar—, pero un fichero
vacío se leería como «nada que objetar».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 11:03:41 +00:00
Sergio 3ab74532af estado: cosecha granja 2026-08-30T10:02:00Z — avance del árbol KDE 2026-08-30 10:02:00 +00:00
SergioandClaude Opus 5 62da4ca128 estado: COSMIC 88/90 — drenadas 22 de las 24 en deuda
Se construyeron en gioser, no en el worker: el LXC dev.gioser.net lleva ~14 h sin ssh.
Quedan `cosmic-applets` y `cosmic-settings`, que superan el techo de memoria de la caja
INCLUSO con un solo core (7,7 G con un suelo de 2,3 G que ponen gitea y los agentes) — no
es paralelismo, es el pico de un `rustc` solo.

El método que las trajo: capar por entorno con `taskset` (nunca `-j` en la fase, que
cambiaría el ArtifactHash) bajando 3→2→1 cores, con un vigía que corta el lote si el swap
libre baja de 250 MB. Cada bajada compró recetas. El vigía cortó cuatro veces y la máquina
sobrevivió las cuatro; el LXC pasó por el mismo pico sin guardián y no volvió.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
2026-08-30 09:50:53 +00:00
Sergio 1e1a4a3943 estado: cosecha granja 2026-08-30T09:32:07Z — avance del árbol KDE 2026-08-30 09:32:07 +00:00
Sergio 542a229f4e estado: cosecha granja 2026-08-30T08:31:56Z — avance del árbol KDE 2026-08-30 08:31:56 +00:00
Sergio 04fe72176f estado: cosecha granja 2026-08-30T08:02:00Z — avance del árbol KDE 2026-08-30 08:02:00 +00:00
Sergio 61bc203049 estado: cosecha granja 2026-08-30T07:01:58Z — avance del árbol KDE 2026-08-30 07:01:58 +00:00
Sergio 6d82113a7c estado: cosecha granja 2026-08-30T06:32:00Z — avance del árbol KDE 2026-08-30 06:32:00 +00:00
Sergio 4627d12ba9 estado: cosecha granja 2026-08-30T06:01:59Z — avance del árbol KDE 2026-08-30 06:01:59 +00:00
Sergio 8496ee06c0 estado: cosecha granja 2026-08-30T05:32:02Z — avance del árbol KDE 2026-08-30 05:32:02 +00:00
Sergio 799328a51b estado: cosecha granja 2026-08-30T04:32:00Z — avance del árbol KDE 2026-08-30 04:32:00 +00:00
SergioandClaude Opus 5 6cd8a0410b tasas: H1 PAGADO — los cuatro kernels de Cards ya traen MEMCG y PSI
hammer kernel contract --sealed: 5 de 5 vigentes cumplen su perfil, exit 0. Antes eran 2 de 11.
Una Card que pide memory.max ahora encuentra el fichero en vez de dejar un warn! y correr sin
tope.

Lo medido, no lo supuesto:
- el x86_64_defconfig de 7.1 ya NO trae MEMCG=y ⇒ había que pedirlo explícito;
- MEMCG no tiene ningún depends on, así que el olddefconfig no podía tragárselo — comprobado
  igual contra el .config producido, que es la regla del guardián;
- precio: +80 KiB en linux-generic (+0,49%), +84 en linux-metal, +88 en linux-metal-dual,
  +80 en linux-gioser (+0,71%). Medio punto de kernel por contabilidad por unidad y presión.

Evidencia cruda en docs/evidencia/tasas-kernel-2026-08-29/contrato-h1-pagado.txt, con el
diff-back del plan de gioser sobre su config nuevo (35 cumplidos, 0 incumplidos).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 04:02:10 +00:00
Sergio 0677ba26cd estado: cosecha granja 2026-08-30T04:01:59Z — avance del árbol KDE 2026-08-30 04:01:59 +00:00
Sergio 53eda53683 estado: cosecha granja 2026-08-30T03:32:13Z — avance del árbol KDE 2026-08-30 03:32:13 +00:00
Sergio 9688a15780 estado: cosecha granja 2026-08-30T03:02:24Z — avance del árbol KDE 2026-08-30 03:02:24 +00:00
Sergio 96d7b818a9 estado: cosecha granja 2026-08-30T02:32:12Z — avance del árbol KDE 2026-08-30 02:32:12 +00:00
Sergio 7e8d9a202a estado: cosecha granja 2026-08-30T02:02:08Z — avance del árbol KDE 2026-08-30 02:02:08 +00:00
SergioandClaude Opus 5 8faede66bd kernel contract: un objetivo sin sellado vigente NO es un objetivo aprobado
Faltaban dos agujeros del mismo tamaño que el que este guardián vino a tapar:

1. La receta derivada de gioser vive fuera de recipes/ a propósito, así que sus deps no
   resolvían y su hash no se podía calcular ⇒ su sellado viejo se comprobaba como si fuera el
   vigente. Ahora se le presta el catálogo (base_dir), salvo que traiga patches — que base_dir
   también los resuelve y moverlo los rompería en silencio.
2. Un objetivo del contrato cuya receta de hoy no tiene NINGÚN sellado simplemente no aparecía
   en el barrido, y no aparecer se leía como que no había nada que objetar. Ahora se nombra con
   el hash que le tocaría y sale != 0: no comprobar no es aprobar.

Hoy eso dice, con nombre y hash, exactamente los 4 kernels que hay que construir para dar H1
por pagado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 01:52:57 +00:00
SergioandClaude Opus 5 697d3f21a1 tasas H4: un test hace regla lo que era suerte — cero pre_exec en el workspace
pre_exec apaga el camino posix_spawn de Rust y devuelve el spawn a fork+exec: 231 µs contra
3 949 µs con 256 MB tocados en el padre (SDD 25 T1). hammer no usa ninguno hoy, pero nadie lo
comprobaba.

El test se comprobó en los dos sentidos: inyectando un pre_exec en sandbox.rs se pone rojo, y
si el barrido no encuentra fuentes también falla — un guardián que aprueba por vacío es el
fallo de CLAUDE.md §3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 01:48:37 +00:00
SergioandClaude Opus 5 292e3ed6cf tasas/runbook: vigente vs superado en §4.ter y el -j1 de gioser, saldado
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 01:45:50 +00:00
SergioandClaude Opus 5 7148c89534 kernel: regenerado el plan de gioser sobre la base con MEMCG+PSI
La receta derivada inlinea la fase configure de su base, así que el MEMCG de linux-metal sólo
llega acá regenerando. Aprovecha el re-hasheo obligado para soltar la concesión del -j1: vuelve
al -j"$(nproc)" que emite el armador, que es la regla del repo — capar el paralelismo en la
receta cambia el ArtifactHash aunque el binario sea idéntico, y el throttling va por entorno.

b3:b026fe14… reemplaza a b3:8ffd5710… (que era la variante -j1 sin MEMCG).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 01:44:25 +00:00
SergioandClaude Opus 5 3c716dfd9f kernel contract: el sellado VIGENTE bloquea, el superado se informa
El store guarda todos los sellados, no el último. Como `--sealed` los miraba a todos por
igual, al cambiar una receta de kernel el gate quedaba rojo para siempre por artefactos que
nadie va a volver a construir — y un portón que no puede ponerse verde deja de leerse.

Ahora clasifica cada sellado contra el ArtifactHash de la receta de hoy (misma vigencia que
`hammer hash --check`, lab incluido): el vigente bloquea, el superado sale en su propia
sección con lo que le falta, porque sigue siendo cierto que una máquina que arranque ese
kernel corre sus Cards sin tope.

Dos negativas explícitas: si no se puede resolver la vigencia se comprueba TODO y se dice por
qué; y cero vigentes con superados a la vista sale != 0 en vez de verde — el vacío leído como
presencia es justo el fallo que este guardián vino a arreglar.

Recetas derivadas incluidas: --recipes mira `recipes/` y `docs/state/kernel-plans/`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 01:42:26 +00:00
SergioandClaude Opus 5 e18cd942c1 kernel: MEMCG y PSI en los tres kernels que hospedan Cards (SDD 25 H1)
Sin CONFIG_MEMCG el fichero memory.max no existe y arje-incarnate descarta el error: una
Card pedía un tope de memoria y corría sin ninguno, dejando sólo un warn!. El x86_64_defconfig
de 7.1 ya no trae MEMCG=y, así que había que pedirlo explícito.

Va en tanda con PSI (que el contrato declara como wants) para pagar de una sola vez el
re-hasheo: la fase configure ES la identidad del artefacto. linux.toml queda intocado a
propósito — es el baseline of_tree del selfhost-verify y no hospeda Cards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-30 01:36:22 +00:00
Sergio 11a87c276d estado: cosecha granja 2026-08-30T01:32:00Z — avance del árbol KDE 2026-08-30 01:32:00 +00:00
Sergio 6382f9f693 estado: cosecha granja 2026-08-30T01:02:04Z — avance del árbol KDE 2026-08-30 01:02:04 +00:00
Sergio a4e74e5f51 estado: cosecha granja 2026-08-30T00:32:06Z — avance del árbol KDE 2026-08-30 00:32:06 +00:00
Sergio bdf18e4e94 estado: cosecha granja 2026-08-30T00:03:04Z — avance del árbol KDE 2026-08-30 00:03:04 +00:00
Sergio d639a5ac1b estado: cosecha granja 2026-08-29T23:32:06Z — avance del árbol KDE 2026-08-29 23:32:06 +00:00
Sergio 78839f3d61 estado: cosecha granja 2026-08-29T23:01:59Z — avance del árbol KDE 2026-08-29 23:01:59 +00:00
Sergio 181da53b8e estado: cosecha granja 2026-08-29T22:32:02Z — avance del árbol KDE 2026-08-29 22:32:02 +00:00
Sergio a8643e0104 estado: cosecha granja 2026-08-29T22:01:58Z — avance del árbol KDE 2026-08-29 22:01:58 +00:00
Sergio c6300e1342 estado: cosecha granja 2026-08-29T21:31:57Z — avance del árbol KDE 2026-08-29 21:31:57 +00:00
Sergio 36c8003646 estado: cosecha granja 2026-08-29T21:02:10Z — avance del árbol KDE 2026-08-29 21:02:10 +00:00
SergioandClaude Opus 5 72159adb32 runbook: el contrato de capacidades, al lado del diff-back
`diff-back` contesta «¿el .config honra el PLAN?»; el contrato contesta la otra
mitad, «¿el kernel trae lo que el userland usa por nombre?». Son distintas: un
kernel puede honrar su plan al pie de la letra y no tener MEMCG, que nunca
estuvo en ningún plan porque nadie lo pidió.

Queda dicho también que hoy sale ROJO a propósito (2 de 11) y por qué.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-29 19:36:17 +00:00
SergioandClaude Opus 5 bfa3c6445a tasas: SDD 25 al día — §4 confirmado sobre artefactos, §9 con cuatro medidos
- §0 y §4.bis: el «sin MEMCG» deja de ser deducción del defconfig. Los ONCE
  .config sellados traen `# CONFIG_MEMCG is not set` y `# CONFIG_PSI is not
  set`, y ninguno tiene CFS_BANDWIDTH (⇒ tampoco existe `cpu.max`; hoy nadie
  lo pide). Y se nombra por qué sobrevivió: el kernel de la máquina de
  desarrollo SÍ los trae — el fallo sólo existe del lado del artefacto.
- §4.ter: el guardián `hammer kernel contract`, con las tres decisiones de
  diseño (por perfil, contra el .config y no la receta, apagado ≠ ausente).
- §3: de tres sorpresas a cinco (taskstats no es más rápido que /proc; el
  sondeo es ciego, no lento).
- §9: reescrita. Cuatro de los seis experimentos nombrados quedaron medidos
  con root; siguen abiertos el iterador BPF (detrás de H7) y el Intel con PTI.
- §5, §7 y §10: filas medidas, H1 marcado como pendiente-pero-vigilado,
  H8 (el contrato es parte del release) y H9 (reflink alimenta SDD 18).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-29 19:34:16 +00:00
Sergio 7cb1a5de95 estado: cosecha granja 2026-08-29T19:31:56Z — avance del árbol KDE 2026-08-29 19:31:56 +00:00
SergioandClaude Opus 5 849c041446 tasas: los 4 experimentos que §9 dejó nombrados, medidos con root
SDD 25 §9 listó seis experimentos que no se pudieron correr y por qué. Cuatro
de ellos sólo pedían privilegios, y esta sesión los tuvo. Programas y salidas
crudas en docs/evidencia/tasas-kernel-2026-08-29/.

1. proc connector (bench_connector.c) — el sondeo de /proc no es lento, es
   CIEGO: 200 procesos de vida mínima lanzados uno cada 10 ms, sondeando cada
   100 ms se ven **0 de 200**; por connector, **200 de 200**. Y esperar el
   evento no cuesta nada frente a waitpid (116,0 vs 114,8 µs, dentro del ruido),
   con 0 eventos perdidos en 1400.
   Sorpresa: taskstats por genetlink **no es más rápido** que parsear
   /proc/self/stat (2 688 vs 2 697 ns). Su valor es el CONTENIDO (delay
   accounting), no la velocidad.

2. reflink (bench_reflink.c) — el «copiar en O(1)» existe y NO está en ext4:
   FICLONE no soportado, y copy_file_range ≈ read+write (13,7 vs 14,5 ms).
   En xfs y btrfs es PLANO: 64 MiB en 0,010 ms y 512 MiB —ocho veces más— en
   0,020 ms, contra 512,7 ms de read+write. **25 600× a 512 MiB.**

3. io_uring SQPOLL (bench_sqpoll.c) — no rescata el veredicto de T12: sobre
   datos cacheados pierde igual. ext4: pread 554 ns < io_uring 618 < SQPOLL 868.
   tmpfs: pread 749 < SQPOLL 1 409 < io_uring 2 030 (ahí sí mejora al io_uring
   normal, 1,4×, pero sigue perdiendo contra la syscall). Y se come un core.

4. syscalls por invocación de compilador — con el strace DEL PROPIO CORPUS
   (store/3c1fd8c0…-strace), que es la respuesta a «no hay strace en esta
   máquina». gcc: 1 803 syscalls por un `-c` trivial, **1 095 con error (61%),
   919 de ellas readlink** — la canonicalización de rutas, que T8 no medía.
   Pero al piso de 78,7 ns son 0,14 ms sobre 30,5 ms de compilado: **el cruce
   al kernel es el 0,5%**. La tormenta de rutas vale milisegundos, no minutos.

Condiciones distintas a la corrida original (load ~1,3 sobre 4 vCPU contra 7,3)
⇒ los absolutos NO se comparan entre corridas, sólo los cocientes dentro de una.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-29 19:31:48 +00:00
SergioandClaude Opus 5 7433bcba83 kernel: un contrato de capacidades que hace RUIDOSA la falta de MEMCG
SDD 25 §4 dejó el hallazgo escrito y sin guardián: los kernels de hammer se
construyen sin CONFIG_MEMCG, `memory.max` no existe, y arje descarta el error
al escribirlo ⇒ una Card pide un tope de memoria, corre SIN tope, y la única
huella es un `warn!`. Nadie lo veía porque la máquina de desarrollo SÍ trae
MEMCG: el fallo sólo existe del lado del artefacto sellado.

`hammer kernel contract` declara qué pedazos de interfaz de kernel usa el
userland POR NOMBRE (con consumidor, fichero y CÓMO FALLA HOY si no está) y los
comprueba contra un `.config` YA PRODUCIDO — no contra la receta: entre el
`scripts/config -e X` y el `.config` hay un `olddefconfig` que puede tragarse el
símbolo en silencio.

Por perfil, no global — misma lección que el gate de hardware: `linux.toml` es
el kernel de QEMU del selfhost-verify, no hospeda Cards, y su hash es
load-bearing del baseline `of_tree`. Exigirle contabilidad de memoria sería
rechazar una receta sana.

Medido sobre el store: **2 de 11 configs sellados cumplen su perfil**; los 9
`anfitrion-cards` fallan por MEMCG (apagado A MANO: `# CONFIG_MEMCG is not
set`) y les falta PSI. El kernel vivo de esta máquina pasa las 11 exigidas —
que es exactamente por qué el bug sobrevivió.

Distingue apagado explícito de ausente (un símbolo que el .config ni nombra
puede ser un renombrado entre versiones), y un kernel sin perfil declarado
queda SIN COMPROBAR en vez de contar como aprobado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
2026-08-29 19:12:11 +00:00
Sergio eec5d8db79 estado: cosecha granja 2026-08-29T19:01:48Z — avance del árbol KDE 2026-08-29 19:01:48 +00:00
SergioandClaude Opus 5 dcf80a17a6 vps-setup: patch faltaba en la rama Fedora — tumbaba el stack GUI de GNOME
En Debian `patch` entra de regalo dentro de `build-essential`; al desglosar la rama dnf a
gcc/g++/make sueltos se cayó sin que nadie lo notara. El campo `patches = [...]` de una
receta lo aplica hammer DEL LADO DEL HOST, fuera del sandbox, así que no alcanza con que
el rootfs lo traiga —lo trae, `/usr/bin/patch` está en el alpine de los dos labs— y por
eso el diagnóstico "rootfs laptop ≠ worker" no aplicaba acá.

Medido en dev.gioser.net: `cairo-shared` moría con `spawn patch: No such file or
directory` y detrás caían `pango` y `gtk4`. El stack GUI entero de GNOME por un binario
de 129 KB ausente en el host.

Regla que queda escrita: toda herramienta que hammer invoque HOST-SIDE va en esta lista,
no en `[deps]` de la receta — declararla en la receta re-hashearía cairo y todo GNOME
debajo, para arreglar algo que no es de la receta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
2026-08-29 19:00:18 +00:00