tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

⎇ main docs/33-versionado-y-relojes.md Markdown · 324 siq'ikuna · 19.9 KB Ñawpa willay ch'uya

docs(sdd33): el §4 pasa de «se puede ejecutar» a «corre» — faltaba el paso que junta las dos mitades f9f0b0332 · Sergio ·

1# SDD 33 — Versionar takana: cómo se llama una versión, qué reloj mira qué, y qué se cambia solo
2
3> **Estado: PROPUESTA, con DOS piezas implementadas y medidas.**
4> — `scripts/fuentes/upstream-vigia.py` (2026-09-23), el reloj de río arriba. **Cableado al cron el
5>   2026-10-07** con puerta diaria: hasta ese día había corrido UNA vez a mano y su informe llevaba
6>   catorce días congelado, que es el modo de falla que §2.1 describe para los guardianes.
7>   Extendido el mismo día a 196 de los 625 tarballs (§5).
8> — `scripts/fuentes/bump-repo.py` (2026-10-07), el **aplicador atómico por repositorio** del §3.1,
9>   con la política del §3.2 metida EN EL COMANDO: con un repo de terceros se niega (rc=2) salvo
10>   `--third-party`. Dos bumps aplicados: tawasuyu (35 recetas, 35/35 selladas) y nuestro propio
11>   repo (5 recetas que estaban en **cinco commits distintos**).
12> El resto está decidido y no construido —§4, rearmar los medios por el CIERRE— y se dice cuál es cuál.
13
14## 0. La pregunta
15
16> *¿cómo ir versionando esto? hay las actualizaciones desde los orígenes que hay que adaptar las
17> recetas, por ejemplo ya está kernel 7.3. y lo que voy desarrollando de tawasuyu que avanza rápido.*
18
19Son **tres** preguntas y conviene no mezclarlas, porque cada una se contesta distinto:
20
211. **Cómo se LLAMA una versión** de takana — hoy no se llama de ninguna manera.
222. **Quién mira qué se movió río arriba**, y cada cuánto.
233. **Qué se puede cambiar solo** y qué no. Ésta es la única con filo.
24
25## 1. Una versión ya existe, y no es un número
26
27La identidad real de una imagen **ya está**: es el hash de la clausura de su perfil. No se puede
28falsificar, no depende de quién la armó y dos imágenes con el mismo hash son la misma imagen. Eso es
29mejor que cualquier número, y es lo que el sistema usa para todo.
30
31Lo que falta es un nombre que una persona pueda decir en voz alta. Propuesta:
32
33| | |
34|---|---|
35| **identidad** | `b3:<hash de la clausura del perfil>` — exacta, la que verifica |
36| **nombre** | `takana AÑO.MES[.n]` — `takana 2026.09`, y `2026.09.2` si hay más de una en el mes |
37| **dónde vive** | en la imagen (`/etc/os-release`), en el nombre del fichero publicado, y lo imprime el instalador antes de tocar el disco |
38
39⚠ **El nombre NO es la versión: es una etiqueta sobre ella.** Un fichero llamado
40`takana-2026.09-mirada.sh` tiene que decir de qué clausura salió, porque el día que alguien
41reconstruya para comprobar, lo que compara es el hash. Publicar sólo el nombre es la forma barata de
42que el sitio empiece a mentir — que es lo que ya pasó con un sha256 pegado a mano en la página.
43
44## 2. Tres relojes, no dos, y ya hay uno andando
45
46| reloj | cada | qué hace | estado |
47|---|---|---|---|
48| **cosecha** | 30 min | trae del worker lo que se selló, siembra recetas, regenera el estado y commitea | **anda** (`scripts/farm/cosecha-cron.sh`) |
49| **río arriba** | 1 día | pregunta a cada fuente si hay algo más nuevo que el pin, y deja un informe | **la pieza implementada hoy** |
50| **medios** | cuando el cierre cambie | rearma imágenes e instaladores y publica | **anda el bucle entero** (§4): el cierre se publica cada ciclo, la imagen lo declara en su `os-release` y `imagen-rancia.py` compara. Falta que haya imágenes con las que comparar |
51
52### 2.1 Río arriba — `scripts/fuentes/upstream-vigia.py`
53
54Pregunta lo que `fuentes-vigia.sh` no pregunta. Aquél mira si la fuente **sigue viva**; éste mira si
55hay **algo más nuevo que lo que tenemos clavado**. Se confunden, y son las dos mitades: una fuente
56viva puede llevar dos años sin tocarse, y una que se movió ayer puede desaparecer mañana.
57
58Medido el 2026-09-23, primera corrida completa:
59
60```
61660 recetas con repo git · 17 s
62462 con un tag más nuevo que el pin
63606 con la rama movida desde el pin
64625 recetas pinean un TARBALL y esta vigía no las mira
65```
66
67**Por qué sólo git, y por qué eso ya es la mitad.** `git ls-remote` funciona contra cualquier
68servidor sin API, sin token y sin clonar — es lo mismo que ya hace `takana pin`. Con eso se cubren
69las 660 que pinean `repo` + `commit`. **Se cuentan y se dicen**, no se adivinan: 625 recetas fuera
70del radar es un dato, no un silencio.
71
72⚠ **Y lo que seguía era falso para el grueso. Corregido el 2026-10-07.** Decía: «un tarball no tiene
73equivalente general: saber si hay una versión nueva pide una pista por receta». Cierto en general,
74falso para la mayor parte — y nadie volvió a medirlo en catorce días:
75
76```
772026-10-07:  196 de github cubiertas con el ls-remote que ya estaba · 429 fuera
782026-10-09:  223 de forja (github+gitlab) · 402 por índice HTTP · 44 fuera
79             625 tarballs → 581 contestados en 104 s con 236 peticiones
80             y con la FECHA del índice: 150 verificados · 230 indicios respaldados
81             · 30 sin fecha · 5 ramas abandonadas descartadas
82```
83
84`.../archive/refs/tags/v1.2.3.tar.gz` y `.../releases/download/1.10/sway-1.10.tar.gz` llevan dentro
85todo lo que hacía falta. No se necesitaba API, ni token, ni campo nuevo: se necesitaba **leer la
86URL**. ⇒ *Una frase que cierra una línea de trabajo hay que volver a medirla: el límite puede haber
87sido del primer intento y no del problema.*
88
89⚠ **Y la primera versión de esa cobertura era un generador de ruido**, que es la otra mitad de la
90lección. Comparar contra el tag más nuevo del repo sin mirar su SERIE da basura en cuanto upstream
91publica varias — **9 falsos de 91, medidos**: `cronie` baja `cronie-1.7.2` y decía «hay v4.3»;
92`bash-completion` baja `2.18.0` y decía «hay 20060301-alt06». La comparación se acota al mismo
93prefijo no numérico y la misma cantidad de componentes, **sesgando hacia el silencio**, y lo
94descartado se cuenta y se dice (11 sin serie comparable, 4 que bajan por sha, 50 sin tags). Un
95guardián cuyos hallazgos son casi todos falsos no se apaga: se deja de leer, y entonces tampoco se
96ve el hallazgo verdadero.
97
98⚠ **Sale 0 siempre.** Un guardián que falla porque upstream sacó una versión nueva grita todos los
99días y en una semana nadie lo mira ([[guardianes-el-ruido-los-mata]]).
100
101## 3. Qué se cambia solo — la única pregunta con filo
102
103> **Se automatiza el pin de lo NUESTRO. No se automatiza el de nadie más.**
104
105### 3.1 tawasuyu: sí, y de a un repo entero
106
107Son **37 recetas sobre el MISMO monorepo**, y ahí está la trampa: bumpear 37 pins de a uno da una
108imagen con 37 commits distintos del mismo árbol — mezcla de versiones que nunca existió junta y que
109nadie probó. El bump tiene que ser **atómico por repositorio**: todas las recetas que comparten
110`repo` saltan al mismo commit, o no salta ninguna.
111
112Es defendible automatizarlo porque es código propio, la granja lo reconstruye y los guardianes ven
113la rotura. Lo que **no** se automatiza ni ahí es publicar: un bump que compila no es un bump que
114arranca.
115
116### 3.2 Terceros: no, y el kernel 7.3 es el ejemplo
117
118Una versión nueva de upstream puede cambiar el sistema de build, las banderas, las dependencias y el
119resultado. Aplicarla sola es cambiar la receta sin leerla.
120
121El kernel lo muestra entero: pasar de 6.16 a 7.3 no es cambiar un número. Toca el contrato de
122kconfig (SDD 22), las opciones que nuestras imágenes exigen (`CONFIG_KEXEC_FILE`, el EFI-stub, el
123`CONFIG_CMDLINE` horneado del que depende el arranque del USB), los drivers de las máquinas que ya
124validamos, y las tasas del SDD 25. **Es una unidad de trabajo, no una línea de cron.**
125
126⇒ Para terceros la vigía **propone**: deja el informe, y quien decide aplica con `takana pin` y una
127construcción. Proponer y aplicar son verbos distintos y conviene que también sean comandos distintos.
128
129## 4. Cuándo rearmar los medios: por el CIERRE, no por el calendario
130
131Un cron semanal que rearma las imágenes gasta horas de worker para producir, casi siempre, **los
132mismos bytes**. El disparador correcto no es el tiempo: es que la clausura del perfil haya cambiado.
133
134```
135por cada perfil publicado:
136    h = hash de la clausura del perfil            (barato: `takana hash`, sin construir)
137    si h ≠ el hash de la imagen publicada:
138        rearmar, probar con los tres arneses, publicar
139```
140
141Y **publicar es lo último**, después de los arneses, nunca antes: hoy son 25 comprobaciones del
142banco, 13 del salto y 13 del USB, y las tres corren sin intervención.
143
144✅ **La primera mitad está construida** (2026-10-10): `scripts/cierre-perfiles.py`, cableado al
145latido y publicando `docs/state/cierres.json`. Ese `h` **no existía** — el grafo sabía el hash de
146cada artefacto y nadie resumía el conjunto — y es lo que convertía este §4 en pseudocódigo
147inejecutable. No construye nada: lee los grafos que el latido acaba de regenerar, en menos de un
148segundo. ⇒ **el `git log` de `cierres.json` ES la señal «este perfil se movió, su imagen quedó
149rancia»**, que hasta hoy no se podía saber sin rearmar para averiguarlo.
150
151⚠ **De qué conjunto, que es la parte que se puede errar sin enterarse: del cierre de IMAGEN.** La
152primera versión sumó las herramientas de `[solo-build]` —`llvm21`, `go`, `python3`, `cmake`,
153`perl`—, que no van en la imagen porque **la imagen no se construye a sí misma**. Entre 17 y 35
154nodos de más por perfil, en pares (cola,nombre):
155
156```
157perfil               build∪runtime  imagen   solo-build de más
158base                           86      69                  17
159cli                           153     133                  20
160escritorio-cosmic             296     261                  35
161escritorio-gnome              369     337                  32
162escritorio-kde                445     411                  34
163escritorio-mirada              41      23                  18
164escritorio-sway               282     249                  33
165servidor                      186     166                  20
166servidor-minimo               117      99                  18
167```
168
169Con el conjunto equivocado, tocar un compilador movía el digest y decía «rearmá» cuando los bytes
170de la imagen no cambian — **un disparador que se enciende por algo que no entra en la imagen es el
171calendario que este §4 quiere evitar.** La membresía sale de `yupana.membresia_imagen()`, la misma
172vista que `hydrate-profile.py` proyecta al FHS: una sola fuente para «qué lleva esta imagen».
173
174Tres clases de miembro, y las tres se nombran en el informe en vez de desaparecer en el número:
175los **propios** entran por su hash; los **`ajeno`** (xwayland, rust-toolchain-bin,
176steam-runtime-sniper) y los **`wanted`** —declarados por el perfil y sin receta que los provea—
177entran por NOMBRE con su marca, porque no se les puede pedir hash y no aportan bytes, pero el día
178que alguien les escriba la receta la marca pasa a hash y el digest se mueve. `escritorio-kde`
179declara `wanted/xwayland`, y el informe lo dice. Y un cierre **incompleto** no se hashea: un digest
180sobre una clausura parcial diría que la imagen cambió cuando lo que cambió es cuánto sabemos.
181
182Los hashes se indexan por **(cola, nombre)**, porque `openssl` es una receta distinta en `corpus` y
183en `incoming-kde`; comprobado que los cinco grafos no se contradicen (1.288 pares, 0 conflictos), y
184si algún día lo hacen el informe lo grita en vez de elegir uno.
185
186✅ **Y la segunda mitad también, el mismo día: la imagen DECLARA de qué clausura salió.**
187`hydrate-profile.py` escribe `/etc/os-release` con `TAKANA_PERFIL`, `TAKANA_NODOS` y
188`TAKANA_CIERRE`. Antes **ninguna imagen declaraba nada** — no se escribía ni un `os-release`; los
189aciertos de `grep` en el repo eran todos lecturas para detectar el anfitrión. Va en ese guion y no
190en cada script de imagen por el mismo motivo que el bit de privilegio: por ahí pasan TODAS las
191imágenes de perfil, y un paso que hay que acordarse de invocar se olvida.
192
193```
194NAME="takana"
195ID=takana
196PRETTY_NAME="takana · metal-tigerlake"
197HOME_URL="https://takana-os.net"
198TAKANA_PERFIL=metal-tigerlake
199TAKANA_NODOS=5
200TAKANA_CIERRE=sha256:5fed4348345bc86498f54c2266d3a7118e646c1bc1685d3baa5f0f822631cac9
201```
202
203**El digest lo calcula la MISMA función que la vigía** (`digest_de`), sobre lo que el guion acaba de
204proyectar. Y eso no es celo: la primera hidratación real dio **dos números distintos** —la vigía
205`5fed4348…`, la imagen `17de5d7e…`— con los cinco hashes idénticos uno a uno. La causa era el
206prefijo: el grafo guarda `b3:7373…` y `takana hash` lo da pelado. Mismo hash, cadena distinta,
207digest distinto. ⇒ **la función normaliza adentro**, porque una función compartida que exige un
208formato convenido de palabra ya son dos implementaciones otra vez. Lo encontró hidratar de verdad y
209comparar, no leer el código.
210
211⚠ **Nada derivado del reloj entra en el fichero**, o la imagen deja de ser reproducible: una fecha
212cambiaría sus bytes cada mes sin que la clausura se mueva — la mentira que el §1 señala. El nombre
213legible es una ETIQUETA y entra sólo con `--version`. Comprobado en los dos sentidos: con
214`--version 2026.10` aparecen `VERSION`/`VERSION_ID` y **el digest no cambia**; y dos hidrataciones
215seguidas dan un `os-release` **byte a byte idéntico**.
216
217⚠ **Un rootfs con artefactos faltantes NO declara digest**: pone `TAKANA_CIERRE=incompleto` y
218cuántos faltan. Un número con pinta de identidad sobre una imagen que no es la clausura que dice
219ser es lo peor de los dos mundos, y el §4 compararía contra una mentira.
220
221✅ **Y el paso que las junta — porque dos mitades que no se encuentran son dos ficheros, no un
222disparador.** `scripts/imagen-rancia.py` lee el `TAKANA_CIERRE` de cada imagen y lo compara con el
223vigente de su perfil; dice cuáles rearmar y no rearma. Cableado al latido con puerta diaria.
224
225Cinco caminos, los cinco probados:
226
227| | |
228|---|---|
229| **al día** | hidratando `metal-tigerlake` de verdad y comparando ⇒ `sha256:5fed4348…` |
230| **rancia** | perturbando el hash de `intel-ucode` en el grafo ⇒ lleva `5fed4348…`, hoy `d6fef70f…` |
231| **rancia** | imagen que declara un digest viejo |
232| **incompleta** | armada con `--allow-missing` ⇒ rearmar |
233| **muda** | imagen anterior al §4 ⇒ «no se puede saber», **no** «al día» |
234
235⚠ Lo último no es un detalle de redacción: **tratar la ausencia de declaración como conformidad es
236la forma exacta de que un medio rancio pase por bueno.**
237
238Y el control de rancidez destapó algo que no se buscaba: saltó la guarda de **hashes contradictorios
239entre grafos** —perturbar uno solo deja a los otros cuatro con el hash viejo— así que dijo RANCIA
240*y* avisó de que la entrada era inconsistente. Una guarda que nunca había disparado, haciendo su
241trabajo.
242
243⇒ **El §4 corre de punta a punta.** Lo que queda no es una pieza que falte: es que no hay imágenes
244publicadas todavía (el sitio no ofrece descargas; en `/srv/repo` hay paquetes `.tkn`). El registro
245`docs/state/imagenes.txt` está vacío a propósito, y existe para que el día que se arme una,
246vigilarla sea **añadir una línea** en vez de acordarse de que esto existe.
247
248## 5. Lo que este documento NO decide
249
250- ~~**La pista por receta para los tarballs** (625)~~ → **la premisa era falsa para el grueso, y
251  196 ya están cubiertas** *(2026-10-07)*. Decía que «hace falta un campo tipo `[source.upstream]`
252  con dónde mirar y qué patrón». Cierto en general; falso para la mayor parte: **196 de los 625
253  tarballs son de `github.com`**, y en esas URLs el repo y la versión **ya están escritos**
254  (`.../archive/refs/tags/v1.2.3.tar.gz`, `.../releases/download/1.10/sway-1.10.tar.gz`), así que
255  el mismo `git ls-remote --tags` del §2.1 las cubre sin API, sin token y sin pista por receta.
256  Primera corrida: **82 bajan una versión más vieja que la publicada**. ⇒ *Una frase que cierra una
257  línea de trabajo hay que volver a medirla: el límite puede haber sido del primer intento y no del
258  problema.*
259
260  **Y las 429 restantes también se cerraron, dos días después** *(2026-10-09)*. No pedían una
261  pista por receta: pedían **un listado por host**, y los hosts estaban concentrados. Dos
262  mecanismos, y el primero tampoco era nuevo: `gitlab.freedesktop.org` (26) es una **forja git**
263  —sus URLs `/-/archive/<tag>/…` llevan el repo escrito, igual que github— y el resto son
264  **índices de directorio HTTP**, que un sondeador genérico lee: 402 recetas con **236 peticiones**
265  gracias a la caché por directorio (las 108 de KDE comparten 13; las 29 de xorg, 3).
266
267  Hace dos sondeos, porque un solo nivel no alcanza: un `<nombre>-<versión-mayor>` en su propio
268  directorio (ftp.gnu.org, xorg, kernel.org) **y una SERIE más nueva** cuando la versión vive en un
269  segmento de la ruta (`stable/frameworks/6.27/`, `sources/libadwaita/1.7/`) — ahí mirar sólo el
270  directorio propio diría «al día» para siempre.
271
272```
273625 tarballs → 581 contestados · 44 sin mirar   (eran 625 ciegos el 2026-10-07)
274  223 de forja  → 150 con versión nueva, 1 sin contestar
275  402 por índice → 265 con un número mayor
276```
277
278  ⚠ **Y un bug propio que valía 53 recetas:** el regex de tags exigía que el tag empezara por
279  dígito, así que descartaba `llvmorg-18.1.8`, `R_2_6_0`, `fuse-3.16.2`, `release-74-2` — **53
280  salían «sin-tags» contra repos que tienen cientos**. El regex laxo se usa SÓLO en el camino del
281  tarball, donde la URL dice qué forma buscar; en el de `repo`+`commit` la estrechez es la
282  protección y aflojarla cambiaría `al_dia` en 660 recetas que hoy se miden bien.
283
284- ~~**Qué hacer con los 265 INDICIOS**~~ → **resuelto el mismo día con la FECHA del índice, y la
285  pieza trae su propio falso negativo medido.** El problema era real: un índice contesta «¿hay un
286  número mayor?», que no es «hay una release más nueva». `gtk3` baja de `sources/gtk+/3.24/` y ese
287  directorio todavía guarda `3.89`…`3.96`, las alfas de GTK4; `3.94 > 3.24` es cierto y
288  semánticamente falso, y ninguna regla numérica lo distingue. **La fecha sí:**
289
290```
291sources/gtk+/   3.24/  2024-Jul-10      ← lo que usamos
292                3.94/  2018-Jun-26      ← seis años más viejo ⇒ rama muerta
293```
294
295  La regla es la que upstream no puede violar sin querer: *nadie vuelve a tocar una rama
296  abandonada*. Hay tres formatos de fecha en tres servidores y hay que cubrir los tres.
297
298  ⚠ **Y el primer intento era demasiado estricto, con un falso negativo que vale como advertencia
299  general:** descartaba el candidato en cuanto su fecha fuera anterior a la nuestra, y entonces
300  `glib 2.90` —serie nueva del 2026-Oct-06, con 2.90.0 y 2.90.1 dentro— quedaba tapada porque
301  `2.88` recibió el parche 2.88.4 **dos días después**. Una rama que sigue recibiendo parches es
302  normal; una abandonada lleva años quieta. El umbral de un año queda lejos de los dos bordes (dos
303  días / seis años), así que no está calibrado contra ningún caso concreto.
304
305  ⇒ Lo que esto compra no es quitar 5 falsos, es **poder decir en qué confiar**: de 260 hallazgos,
306  **230 con la fecha respaldándolos** y 30 sin fecha comprobable. Nunca se descarta por FALTA de
307  fecha — eso sería perder información real por no tener la corroboración — y los niveles van
308  separados en el resumen, porque mezclarlos haría que los 230 buenos se tiraran con los 30
309  dudosos.
310
311- **Los 44 que no contesta nadie**, con nombre: 8 de `files.pythonhosted.org` (rutas con hash ⇒
312  pide la API de PyPI), 5 de `downloads.sourceforge.net` (descarga por redirección), 5 que pinean
313  por sha a propósito, 3 con `no-hay-upstream.invalid` —que es un marcador NUESTRO y está bien así—,
314  3 de `static.rust-lang.org`, y una cola de dos en dos.
315
316- **Cuándo re-pinear `recipes/takana.toml`**, que pinea el repo que la contiene. El bump atómico del
317  2026-10-07 dejó las 5 recetas de nuestro repo en el mismo commit —estaban en **cinco distintos**—
318  pero esa receta vuelve a quedar por detrás en el commit siguiente, el del propio bump. Perseguir
319  «al día» no tiene sentido; lo que vale es que las cinco compartan commit. **Re-pinear es una
320  decisión de release, y no está tomada.**
321- **Si el nombre lleva mes o semana.** Depende de cada cuánto se publique de verdad, y eso todavía
322  no se sabe porque hoy se publica a mano.
323- **Firmar lo publicado.** Los `.sha256` de al lado comprueban la descarga, no la procedencia. El
324  repo de paquetes ya tiene firma (ADR 0014); las imágenes no, y son lo que más se copia.

33-versionado-y-relojes.md

SDD 33 — Versionar takana: cómo se llama una versión, qué reloj mira qué, y qué se cambia solo

Estado: PROPUESTA, con DOS piezas implementadas y medidas. — scripts/fuentes/upstream-vigia.py (2026-09-23), el reloj de río arriba. Cableado al cron el 2026-10-07 con puerta diaria: hasta ese día había corrido UNA vez a mano y su informe llevaba catorce días congelado, que es el modo de falla que §2.1 describe para los guardianes. Extendido el mismo día a 196 de los 625 tarballs (§5). — scripts/fuentes/bump-repo.py (2026-10-07), el aplicador atómico por repositorio del §3.1, con la política del §3.2 metida EN EL COMANDO: con un repo de terceros se niega (rc=2) salvo --third-party. Dos bumps aplicados: tawasuyu (35 recetas, 35/35 selladas) y nuestro propio repo (5 recetas que estaban en cinco commits distintos). El resto está decidido y no construido —§4, rearmar los medios por el CIERRE— y se dice cuál es cuál.

0. La pregunta

¿cómo ir versionando esto? hay las actualizaciones desde los orígenes que hay que adaptar las recetas, por ejemplo ya está kernel 7.3. y lo que voy desarrollando de tawasuyu que avanza rápido.

Son tres preguntas y conviene no mezclarlas, porque cada una se contesta distinto:

  1. Cómo se LLAMA una versión de takana — hoy no se llama de ninguna manera.
  2. Quién mira qué se movió río arriba, y cada cuánto.
  3. Qué se puede cambiar solo y qué no. Ésta es la única con filo.

1. Una versión ya existe, y no es un número

La identidad real de una imagen ya está: es el hash de la clausura de su perfil. No se puede falsificar, no depende de quién la armó y dos imágenes con el mismo hash son la misma imagen. Eso es mejor que cualquier número, y es lo que el sistema usa para todo.

Lo que falta es un nombre que una persona pueda decir en voz alta. Propuesta:

identidadb3:<hash de la clausura del perfil> — exacta, la que verifica
nombretakana AÑO.MES[.n] — takana 2026.09, y 2026.09.2 si hay más de una en el mes
dónde viveen la imagen (/etc/os-release), en el nombre del fichero publicado, y lo imprime el instalador antes de tocar el disco

⚠ El nombre NO es la versión: es una etiqueta sobre ella. Un fichero llamado takana-2026.09-mirada.sh tiene que decir de qué clausura salió, porque el día que alguien reconstruya para comprobar, lo que compara es el hash. Publicar sólo el nombre es la forma barata de que el sitio empiece a mentir — que es lo que ya pasó con un sha256 pegado a mano en la página.

2. Tres relojes, no dos, y ya hay uno andando

relojcadaqué haceestado
cosecha30 mintrae del worker lo que se selló, siembra recetas, regenera el estado y commiteaanda (scripts/farm/cosecha-cron.sh)
río arriba1 díapregunta a cada fuente si hay algo más nuevo que el pin, y deja un informela pieza implementada hoy
medioscuando el cierre cambierearma imágenes e instaladores y publicaanda el bucle entero (§4): el cierre se publica cada ciclo, la imagen lo declara en su os-release y imagen-rancia.py compara. Falta que haya imágenes con las que comparar

2.1 Río arriba — scripts/fuentes/upstream-vigia.py

Pregunta lo que fuentes-vigia.sh no pregunta. Aquél mira si la fuente sigue viva; éste mira si hay algo más nuevo que lo que tenemos clavado. Se confunden, y son las dos mitades: una fuente viva puede llevar dos años sin tocarse, y una que se movió ayer puede desaparecer mañana.

Medido el 2026-09-23, primera corrida completa:

660 recetas con repo git · 17 s
462 con un tag más nuevo que el pin
606 con la rama movida desde el pin
625 recetas pinean un TARBALL y esta vigía no las mira

Por qué sólo git, y por qué eso ya es la mitad. git ls-remote funciona contra cualquier servidor sin API, sin token y sin clonar — es lo mismo que ya hace takana pin. Con eso se cubren las 660 que pinean repo + commit. Se cuentan y se dicen, no se adivinan: 625 recetas fuera del radar es un dato, no un silencio.

⚠ Y lo que seguía era falso para el grueso. Corregido el 2026-10-07. Decía: «un tarball no tiene equivalente general: saber si hay una versión nueva pide una pista por receta». Cierto en general, falso para la mayor parte — y nadie volvió a medirlo en catorce días:

2026-10-07:  196 de github cubiertas con el ls-remote que ya estaba · 429 fuera
2026-10-09:  223 de forja (github+gitlab) · 402 por índice HTTP · 44 fuera
             625 tarballs → 581 contestados en 104 s con 236 peticiones
             y con la FECHA del índice: 150 verificados · 230 indicios respaldados
             · 30 sin fecha · 5 ramas abandonadas descartadas

.../archive/refs/tags/v1.2.3.tar.gz y .../releases/download/1.10/sway-1.10.tar.gz llevan dentro todo lo que hacía falta. No se necesitaba API, ni token, ni campo nuevo: se necesitaba leer la URL. ⇒ Una frase que cierra una línea de trabajo hay que volver a medirla: el límite puede haber sido del primer intento y no del problema.

⚠ Y la primera versión de esa cobertura era un generador de ruido, que es la otra mitad de la lección. Comparar contra el tag más nuevo del repo sin mirar su SERIE da basura en cuanto upstream publica varias — 9 falsos de 91, medidos: cronie baja cronie-1.7.2 y decía «hay v4.3»; bash-completion baja 2.18.0 y decía «hay 20060301-alt06». La comparación se acota al mismo prefijo no numérico y la misma cantidad de componentes, sesgando hacia el silencio, y lo descartado se cuenta y se dice (11 sin serie comparable, 4 que bajan por sha, 50 sin tags). Un guardián cuyos hallazgos son casi todos falsos no se apaga: se deja de leer, y entonces tampoco se ve el hallazgo verdadero.

⚠ Sale 0 siempre. Un guardián que falla porque upstream sacó una versión nueva grita todos los días y en una semana nadie lo mira ([[guardianes-el-ruido-los-mata]]).

3. Qué se cambia solo — la única pregunta con filo

Se automatiza el pin de lo NUESTRO. No se automatiza el de nadie más.

3.1 tawasuyu: sí, y de a un repo entero

Son 37 recetas sobre el MISMO monorepo, y ahí está la trampa: bumpear 37 pins de a uno da una imagen con 37 commits distintos del mismo árbol — mezcla de versiones que nunca existió junta y que nadie probó. El bump tiene que ser atómico por repositorio: todas las recetas que comparten repo saltan al mismo commit, o no salta ninguna.

Es defendible automatizarlo porque es código propio, la granja lo reconstruye y los guardianes ven la rotura. Lo que no se automatiza ni ahí es publicar: un bump que compila no es un bump que arranca.

3.2 Terceros: no, y el kernel 7.3 es el ejemplo

Una versión nueva de upstream puede cambiar el sistema de build, las banderas, las dependencias y el resultado. Aplicarla sola es cambiar la receta sin leerla.

El kernel lo muestra entero: pasar de 6.16 a 7.3 no es cambiar un número. Toca el contrato de kconfig (SDD 22), las opciones que nuestras imágenes exigen (CONFIG_KEXEC_FILE, el EFI-stub, el CONFIG_CMDLINE horneado del que depende el arranque del USB), los drivers de las máquinas que ya validamos, y las tasas del SDD 25. Es una unidad de trabajo, no una línea de cron.

⇒ Para terceros la vigía propone: deja el informe, y quien decide aplica con takana pin y una construcción. Proponer y aplicar son verbos distintos y conviene que también sean comandos distintos.

4. Cuándo rearmar los medios: por el CIERRE, no por el calendario

Un cron semanal que rearma las imágenes gasta horas de worker para producir, casi siempre, los mismos bytes. El disparador correcto no es el tiempo: es que la clausura del perfil haya cambiado.

por cada perfil publicado:
    h = hash de la clausura del perfil            (barato: `takana hash`, sin construir)
    si h ≠ el hash de la imagen publicada:
        rearmar, probar con los tres arneses, publicar

Y publicar es lo último, después de los arneses, nunca antes: hoy son 25 comprobaciones del banco, 13 del salto y 13 del USB, y las tres corren sin intervención.

✅ La primera mitad está construida (2026-10-10): scripts/cierre-perfiles.py, cableado al latido y publicando docs/state/cierres.json. Ese h no existía — el grafo sabía el hash de cada artefacto y nadie resumía el conjunto — y es lo que convertía este §4 en pseudocódigo inejecutable. No construye nada: lee los grafos que el latido acaba de regenerar, en menos de un segundo. ⇒ el git log de cierres.json ES la señal «este perfil se movió, su imagen quedó rancia», que hasta hoy no se podía saber sin rearmar para averiguarlo.

⚠ De qué conjunto, que es la parte que se puede errar sin enterarse: del cierre de IMAGEN. La primera versión sumó las herramientas de [solo-build] —llvm21, go, python3, cmake, perl—, que no van en la imagen porque la imagen no se construye a sí misma. Entre 17 y 35 nodos de más por perfil, en pares (cola,nombre):

perfil               build∪runtime  imagen   solo-build de más
base                           86      69                  17
cli                           153     133                  20
escritorio-cosmic             296     261                  35
escritorio-gnome              369     337                  32
escritorio-kde                445     411                  34
escritorio-mirada              41      23                  18
escritorio-sway               282     249                  33
servidor                      186     166                  20
servidor-minimo               117      99                  18

Con el conjunto equivocado, tocar un compilador movía el digest y decía «rearmá» cuando los bytes de la imagen no cambian — un disparador que se enciende por algo que no entra en la imagen es el calendario que este §4 quiere evitar. La membresía sale de yupana.membresia_imagen(), la misma vista que hydrate-profile.py proyecta al FHS: una sola fuente para «qué lleva esta imagen».

Tres clases de miembro, y las tres se nombran en el informe en vez de desaparecer en el número: los propios entran por su hash; los ajeno (xwayland, rust-toolchain-bin, steam-runtime-sniper) y los wanted —declarados por el perfil y sin receta que los provea— entran por NOMBRE con su marca, porque no se les puede pedir hash y no aportan bytes, pero el día que alguien les escriba la receta la marca pasa a hash y el digest se mueve. escritorio-kde declara wanted/xwayland, y el informe lo dice. Y un cierre incompleto no se hashea: un digest sobre una clausura parcial diría que la imagen cambió cuando lo que cambió es cuánto sabemos.

Los hashes se indexan por (cola, nombre), porque openssl es una receta distinta en corpus y en incoming-kde; comprobado que los cinco grafos no se contradicen (1.288 pares, 0 conflictos), y si algún día lo hacen el informe lo grita en vez de elegir uno.

✅ Y la segunda mitad también, el mismo día: la imagen DECLARA de qué clausura salió. hydrate-profile.py escribe /etc/os-release con TAKANA_PERFIL, TAKANA_NODOS y TAKANA_CIERRE. Antes ninguna imagen declaraba nada — no se escribía ni un os-release; los aciertos de grep en el repo eran todos lecturas para detectar el anfitrión. Va en ese guion y no en cada script de imagen por el mismo motivo que el bit de privilegio: por ahí pasan TODAS las imágenes de perfil, y un paso que hay que acordarse de invocar se olvida.

NAME="takana"
ID=takana
PRETTY_NAME="takana · metal-tigerlake"
HOME_URL="https://takana-os.net"
TAKANA_PERFIL=metal-tigerlake
TAKANA_NODOS=5
TAKANA_CIERRE=sha256:5fed4348345bc86498f54c2266d3a7118e646c1bc1685d3baa5f0f822631cac9

El digest lo calcula la MISMA función que la vigía (digest_de), sobre lo que el guion acaba de proyectar. Y eso no es celo: la primera hidratación real dio dos números distintos —la vigía 5fed4348…, la imagen 17de5d7e…— con los cinco hashes idénticos uno a uno. La causa era el prefijo: el grafo guarda b3:7373… y takana hash lo da pelado. Mismo hash, cadena distinta, digest distinto. ⇒ la función normaliza adentro, porque una función compartida que exige un formato convenido de palabra ya son dos implementaciones otra vez. Lo encontró hidratar de verdad y comparar, no leer el código.

⚠ Nada derivado del reloj entra en el fichero, o la imagen deja de ser reproducible: una fecha cambiaría sus bytes cada mes sin que la clausura se mueva — la mentira que el §1 señala. El nombre legible es una ETIQUETA y entra sólo con --version. Comprobado en los dos sentidos: con --version 2026.10 aparecen VERSION/VERSION_ID y el digest no cambia; y dos hidrataciones seguidas dan un os-release byte a byte idéntico.

⚠ Un rootfs con artefactos faltantes NO declara digest: pone TAKANA_CIERRE=incompleto y cuántos faltan. Un número con pinta de identidad sobre una imagen que no es la clausura que dice ser es lo peor de los dos mundos, y el §4 compararía contra una mentira.

✅ Y el paso que las junta — porque dos mitades que no se encuentran son dos ficheros, no un disparador. scripts/imagen-rancia.py lee el TAKANA_CIERRE de cada imagen y lo compara con el vigente de su perfil; dice cuáles rearmar y no rearma. Cableado al latido con puerta diaria.

Cinco caminos, los cinco probados:

al díahidratando metal-tigerlake de verdad y comparando ⇒ sha256:5fed4348…
ranciaperturbando el hash de intel-ucode en el grafo ⇒ lleva 5fed4348…, hoy d6fef70f…
ranciaimagen que declara un digest viejo
incompletaarmada con --allow-missing ⇒ rearmar
mudaimagen anterior al §4 ⇒ «no se puede saber», no «al día»

⚠ Lo último no es un detalle de redacción: tratar la ausencia de declaración como conformidad es la forma exacta de que un medio rancio pase por bueno.

Y el control de rancidez destapó algo que no se buscaba: saltó la guarda de hashes contradictorios entre grafos —perturbar uno solo deja a los otros cuatro con el hash viejo— así que dijo RANCIA y avisó de que la entrada era inconsistente. Una guarda que nunca había disparado, haciendo su trabajo.

⇒ El §4 corre de punta a punta. Lo que queda no es una pieza que falte: es que no hay imágenes publicadas todavía (el sitio no ofrece descargas; en /srv/repo hay paquetes .tkn). El registro docs/state/imagenes.txt está vacío a propósito, y existe para que el día que se arme una, vigilarla sea añadir una línea en vez de acordarse de que esto existe.

5. Lo que este documento NO decide

625 tarballs → 581 contestados · 44 sin mirar   (eran 625 ciegos el 2026-10-07)
  223 de forja  → 150 con versión nueva, 1 sin contestar
  402 por índice → 265 con un número mayor

⚠ Y un bug propio que valía 53 recetas: el regex de tags exigía que el tag empezara por dígito, así que descartaba llvmorg-18.1.8, R_2_6_0, fuse-3.16.2, release-74-2 — 53 salían «sin-tags» contra repos que tienen cientos. El regex laxo se usa SÓLO en el camino del tarball, donde la URL dice qué forma buscar; en el de repo+commit la estrechez es la protección y aflojarla cambiaría al_dia en 660 recetas que hoy se miden bien.

sources/gtk+/   3.24/  2024-Jul-10      ← lo que usamos
                3.94/  2018-Jun-26      ← seis años más viejo ⇒ rama muerta

La regla es la que upstream no puede violar sin querer: nadie vuelve a tocar una rama abandonada. Hay tres formatos de fecha en tres servidores y hay que cubrir los tres.

⚠ Y el primer intento era demasiado estricto, con un falso negativo que vale como advertencia general: descartaba el candidato en cuanto su fecha fuera anterior a la nuestra, y entonces glib 2.90 —serie nueva del 2026-Oct-06, con 2.90.0 y 2.90.1 dentro— quedaba tapada porque 2.88 recibió el parche 2.88.4 dos días después. Una rama que sigue recibiendo parches es normal; una abandonada lleva años quieta. El umbral de un año queda lejos de los dos bordes (dos días / seis años), así que no está calibrado contra ningún caso concreto.

⇒ Lo que esto compra no es quitar 5 falsos, es poder decir en qué confiar: de 260 hallazgos, 230 con la fecha respaldándolos y 30 sin fecha comprobable. Nunca se descarta por FALTA de fecha — eso sería perder información real por no tener la corroboración — y los niveles van separados en el resumen, porque mezclarlos haría que los 230 buenos se tiraran con los 30 dudosos.