d515ea9fc7d0194d24581d41b3965af0470488e2
2549
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d515ea9fc7 | estado: cosecha granja 2026-09-12T03:33:22Z — avance del árbol KDE | ||
|
|
f2bffd9e4b |
planear: cruzar la config contra las decisiones — y los puertos se adjudicaban por NOMBRE
La config del servidor declara de qué servicios depende: cada `reverse_proxy` y cada `php_fastcgi` apuntan a algo. Cruzarlo contra la decisión tomada sobre cada servicio caza un fallo que ninguna otra parte ve: **el dominio se MUDA y el servicio que lo sirve está marcado MUERE**. Las dos decisiones son razonables por separado y juntas dejan el sitio nuevo devolviendo 502 — no lo ve el DNS, no lo ven los procesos, y no lo ve quien decide de a una entrada por vez, que es como se decide. En gioser apareció el revés: dos sitios proxean a un puerto que NINGÚN servicio censado sirve, o sea que ya devuelven 502 hoy, en el origen — `mail.sigma.gioser.net` → :9000 y `api.gioser.net` → :8000. Confirmado aparte con `ss -lntp`: no hay nada escuchando en ninguno de los dos. **Y un falso positivo que hubo que matar primero.** La primera corrida acusaba a `sergio.gioser.net` de mudarse dejando atrás a `shuma`. Falso, y la causa estaba en el CENSO: los puertos se adjudicaban por prefijo de nombre (`proc.startswith(name[:15])`), así que un `shuma` DECLARADO-MUERTO se quedaba con el 7378 — que lo escucha `shuma-gateway` (pid 294, verificado con `ss -lntp`). Un declarado-muerto por definición no puede estar escuchando. Ahora se atan por PID, que es lo único sin ambigüedad, con caída al nombre sólo para servicios VIVOS cuando `ss` no da el pid. El puerto es la señal más fuerte de que algo sirve, así que colgárselo al servicio equivocado envenena todo lo que se derive de él — acá se derivó un guardián acusando al inocente, que es la forma más rápida de que un guardián se deje de leer. De paso, el lector de Caddy entiende `php_fastcgi` (antes caía en «directiva no reconocida») y registra a dónde proxea cada sitio, incluidos los bloques anidados. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
53de4e259a | estado: cosecha granja 2026-09-12T03:03:23Z — avance del árbol KDE | ||
|
|
8cdaceb3d6 | SDD 26 §6.4: los tres bloqueos de qullqa, medidos — y el primero es que el kernel no trae FUSE | ||
|
|
f123bc99cc | SDD 26 §6.3.ter: lo medido fuera de la jaula (3/3) y lo que todavía espera el lock | ||
|
|
053e5e7edf |
planear: buscar la receta por el PAQUETE del origen — y el intérprete no es el programa
`origen_binario` buscaba la receta por el nombre del SERVICIO, y ése casi nunca es el nombre del paquete: `sshd` lo trae `openssh`, `crond` lo trae `cronie`. El censo ya le preguntó al gestor de paquetes quién posee cada binario, así que ese nombre también se prueba. El perfil pasó de 4 recetas a 7, y los 27 servicios quedan: receta-takana 9 · suelto 9 · paquete-ajeno 13 · interprete 4 · borrado 4. **La trampa, que es la que haría mentir al perfil.** `openclaw` lo posee el paquete `nodejs`; `fail2ban-server`, `glances` y `uvicorn` los posee `python`. Contarlos como cubiertos porque existe `recipes/nodejs.toml` haría salir el perfil N/N describiendo un servidor al que le faltan CUATRO programas. Tienen clase propia (`interprete`) y cuentan las DOS cosas a la vez, porque las dos son ciertas: su runtime entra al perfil —`openclaw` necesita `nodejs` en la imagen pase lo que pase— y el servicio sigue listado como NO cubierto. Meterlo sólo en las raíces miente; dejarlo sólo en los faltantes arma una imagen sin runtime y el programa, cuando llegue, no arranca. Y el paquete del origen no se llama igual que la receta ni para el mismo intérprete: Artix empaqueta `python` y el catálogo tiene `python3.toml`. Sin ese alias tres servicios decían «hace falta una receta takana» teniendo el runtime sellado — dos trabajos muy distintos. **Sellada y sin sellar tampoco son lo mismo**, y el perfil las listaba igual: una sellada se INSTALA del repo firmado, una sin sellar hay que CONSTRUIRLA. Salió con un caso real — `recipes/qdrant.toml` entró al catálogo desde otro frente mientras se escribía esto y no tiene artefacto. Ahora se marca en la línea y se resume al pie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
fb3a326dfd |
latido: los dos vigías nuevos entran al cron — un guardián que hay que acordarse de invocar no existe
`vigia-subcomandos.py` y `hydrate-profile.py --auditar-raices` nacieron ayer encontrando cosas
reales: 46 binarios sellados que no se pueden invocar por falta de driver, y 4 comandos de `bzip2`
apuntando a `/out/usr/bin/…`. **Las dos las encontré a mano, y eso no se repite solo.**
El argumento ya estaba escrito tres líneas más abajo en este mismo fichero, al lado de
`vigia-sonames`, y costó caro: nadie lo corría, así que `libstdc++.so.6` —que rompía el navegador en
los CUATRO perfiles— estuvo en su salida meses sin que nadie la leyera.
Van FUERA de la puerta diaria porque son baratos: **4 s y 22 s** medidos. Dentro del `if` del sello
correrían una vez al día sin motivo.
⚠ **Y la primera versión de este commit los metió DENTRO de la puerta**, justo lo contrario de lo que
decía su propio comentario — el ciclo de prueba no imprimió ni una línea de ellos y así se vio. Por
eso se corre el ciclo de verdad antes de dar por bueno un cablazo al cron: un bloque mal colocado en
un script desatendido no avisa, simplemente no pasa nada.
Dejan fichero en `docs/state/` con la FECHA DE MEDICIÓN por delante, por la misma razón que el
static-audit: con el frente en verde el texto es constante, `git diff --cached --quiet` no vería
cambio, y dentro de tres meses el fichero sería indistinguible de uno rancio. Con la fecha, cada
ciclo deja huella en el `git log` — se ve que el vigía sigue VIVO, no sólo que el último veredicto
fue bueno.
Verificado corriendo el ciclo completo:
subcomandos.txt ✓ TOTAL: 44 herramientas selladas que no se pueden invocar.
raices.txt ✓ ✓ 0 ofensores NUEVOS sobre 1 artefactos.
==> estado commiteado+pusheado
|
||
|
|
6f482edd65 | estado: cosecha granja 2026-09-12T02:34:52Z — avance del árbol KDE | ||
|
|
34f30bfe2b |
ia-modelo-embeddings: el guardián probado EN EL HUB antes de gastar un turno de build — tres fallos
El guardián nuevo (tokeniza español + el espacio distingue) se corrió a mano contra el modelo antes de esperar el lock, y estaba mal de tres formas distintas: 1. **`cos` es una función interna de awk**, así que `cos[2]` muere con un `syntax error` que no menciona el nombre. Se llama `cs`. 2. el `sed` que partía por corchete de apertura **se comía el último vector** (salían 2 de 3, y el `test -ge 3` lo habría cazado, pero fallando por el motivo equivocado). Ahora `grep -o` por el corchete completo. 3. y los backslashes iban DOBLES: en un literal TOML de comilla triple no se escapan, así que la shell recibía `\\n` y `tr` se ponía a borrar las letras «n». Y una cuarta, de mi propio comentario: al explicar el punto 3 escribí la comilla triple **dentro** de la cadena que empieza con comilla triple. Cerró el literal y el TOML dejó de parsear, con el error apuntando a la línea del comentario. Queda avisado ahí mismo. Verificado en el hub: guardián 2 → 11 y 14 tokens distintos, 0 comunes; guardián 3 → gato~perro=0,8436 contra gato~cortafuegos=0,6292. Los dos PASAN con el modelo bueno. |
||
|
|
edc95325d9 |
censar: cuatro binarios que YA NO EXISTEN en disco — y el paso de rescate, que caduca
`/proc/<pid>/exe` termina en « (deleted)» para cuatro servicios de gioser: el fichero ya no está en
disco, sólo vive el inodo que sostiene su proceso. Y en tres de los cuatro hay AHORA otro fichero en
la misma ruta, de distinto tamaño:
tejido corre 12750368 B · en su ruta hay 13815864 B
shuma-gateway corre 8431896 B · en su ruta hay 10363504 B
pacha-secretos corre 8634240 B · en su ruta hay 8647456 B
puerta-f6e393ff corre 197262440 B · en su ruta NO HAY NADA
Copiar la ruta NO FALLA: muda otra cosa, y el servicio nuevo no es el que estaba andando. Todo verde,
todo distinto — el modo de fallo más caro que hay en este frente.
Se recuperan leyendo `/proc/<pid>/exe`, y SÓLO mientras el proceso viva. En una mudanza que termina
BORRANDO el origen, un reinicio de gioser antes de este paso los pierde para siempre. Por eso:
· clase propia en `origen_binario` (`borrado`), no una coletilla dentro del texto de `suelto`: no es
«hay que llevarlo», es «se pierde en el próximo reinicio y el que está en su ruta no es el mismo».
El motivo trae el comando literal de rescate con su pid.
· paso `rescate` en el plan, ANTES del preflight, porque es el único paso que puede volverse
IMPOSIBLE mientras se piensa el resto.
· su verificación COMPARA TAMAÑOS contra el que corre. No es celo: un `cat` de un `/proc` que ya no
existe crea un fichero VACÍO y devuelve 0, así que sin comparar el rescate «pasa». Probado en los
dos sentidos — el paso real sale 0, y con un fichero vacío a propósito dice `FALTA tejido` y sale 1.
Los cuatro ya están rescatados en `work/mudanza/rescate/` (gitignored), byte a byte iguales a los que
corren, con su SHA256SUMS.
Y el empalme se hizo comprobando que el marcador fuera ÚNICO antes de cortar, que es la lección del
`def pasos` duplicado de ayer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
c78c5ff91f |
ia: el modelo de embeddings elegido NO servía — Qwen3-Embedding-0.6B en su lugar, y un guardián que lo habría cazado
multilingual-e5-small sellaba, cargaba, contestaba 384 dimensiones y 45 tests en verde. Y con el modelo de verdad, de punta a punta, el ranking devolvía SIEMPRE la misma página. Seis pasos descartando hipótesis —batching, posición, nuestro código, la cuantización, la conversión— hasta que `/tokenize` lo dijo en una línea: «cortafuegos», «minino», «duerme» y «tejado» van todos al id 100 = `<unk>`. Un vocabulario XLM-RoBERTa por esta ruta deja casi todo en desconocido, y un texto que es todo `<unk>` embebe igual que cualquier otro. La pista estaba a la vista desde el principio: `gato~perro` y `gato~cortafuegos` daban el mismo número a CUATRO DECIMALES. En su lugar, Qwen3-Embedding-0.6B Q8_0, GGUF oficial de Qwen (Apache-2.0): tokeniza español de verdad (`cort|af|uegos`), acierta 3/3 con márgenes anchos (0,649 contra 0,237), y es de la misma familia que el modelo de chat. Cuesta 610 MiB en vez de 126: es el precio de que funcione, y sube la cuenta de la imagen a ~1,85 GiB si se declara. ⚠ Y LA PARTE QUE IMPORTA PARA LA PRÓXIMA VEZ: el guardián del `install` ya no mira sólo el mágico y el tamaño —«existe» no es «sirve»—. Ahora tokeniza dos frases en español sin palabras en común y exige que no compartan tokens (con el e5 roto compartían la mitad, todos `<unk>`), y después levanta el servidor de verdad y exige que un gato se parezca más a un perro que a un cortafuegos. Con esos dos chequeos el e5 no habría sellado nunca. El tar del modelo roto se borró del mirror (476 MB) y del disco. ⚠ El build está en la cola del flock detrás de otro agente (pixi, 22 min y contando), así que el artefacto todavía no está sellado y los guardianes del navegador no corrieron. Lo medido hasta acá: el camino completo host→motor→índice con el modelo nuevo, 3/3 (`archive.ask` de punta a punta, fuera de la jaula), más 45 tests en tawasuyu. |
||
|
|
5f2f502008 | estado: cosecha granja 2026-09-12T02:03:24Z — avance del árbol KDE | ||
|
|
0f3b137578 |
censar: leer la config del servidor web — cinco sitios sirven un root que NO EXISTE
La sonda DNS dice si un dominio resuelve; no si el servidor tiene algo que servirle. Eso lo dice el
FICHERO DE CONFIGURACIÓN, y leerlo no toca al origen: ni una petición, ni riesgo de fail2ban.
Para leerlo se agregó un LECTOR de Caddy al centro (`formatos/caddy.py` ya tenía el escritor), y el
censo lo usa en vez de tener su propio parser a medias — los de nginx y apache ya existían, y dos
parsers del mismo formato es cómo se separan sin que nadie lo note. Cuarto par de la familia web.
Sobre el Caddyfile real de gioser, cinco sitios apuntan a un `root` que no existe — y son justo los
que devolvían los 502 que en su día hicieron que el censo SE BANEARA A SÍ MISMO al sondearlos:
aura.gioser.net → /var/www/aura_frontend · sigma → /var/www/sigma/frontend
summa → /var/www/summa/frontend · kosmofono → … · dev.summa → …
Es evidencia MÁS FUERTE que el DNS: no hay nada que servir, devuelve 502 resuelva donde resuelva. Va
como recomendación `muere` con la ruta y el fichero donde está el bloque.
**Y sirve para lo contrario, que es donde el aviso hacía daño.** Un directorio que la config SÍ
referencia no es huérfano: el plan marcaba `/var/www/git-tawasuyu` como «nadie lo recuerda» estando
servido, y ese aviso aplicado tira `git.tawasuyu.net`.
Dos bugs propios, los dos encontrados contra el fichero real y no sobre un ejemplo mío:
· El `root` de ese sitio vive DENTRO de un `handle`, y yo saltaba los bloques anidados enteros por no
saber modelarlos. Que el pivote no sepa MODELAR algo no es razón para no VERLO: el bloque sigue
marcado SIN-TRADUCIR pero sus `root`/`reverse_proxy` se leen. Con eso aparecieron dos raíces
ausentes más, también anidadas.
· Detectar la cabecera de sitio con una lista negra de directivas dejaba pasar `log { output file … {`
y el snippet `(acceso) {`: DOS dominios inventados que el censo habría puesto a decidir. La regla
que aguanta es positiva — todos los tokens de la cabecera tienen que PARECER una dirección.
Sin regresión: `nginx → caddy` y `apache → caddy` siguen dando `Valid configuration` con el caddy del
corpus.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
30d6f5e9f3 | estado: cosecha granja 2026-09-12T01:33:21Z — avance del árbol KDE | ||
|
|
0ca88fdcd5 |
atuq §6.3.ter: preguntarle al archivo por lo que DECÍA, no por las palabras exactas
El muro no era un daemon ni un LLM — y el propio comentario de la extensión lo decía mal, copiando lo que hace willay-rag. Medido: para ordenar por parecido no hace falta ningún LLM (eso es un coseno) y el «daemon de embeddings» resultó ser el mismo llama-server que ya levanta el chat, con otro modelo. La corrección quedó escrita donde estaba la afirmación. Del lado de la suite (tawasuyu 22527f7d1 y b4ecffea8): el protocolo de llama-server salió a `shared/foreign-llama` (regla 4 — vivía dentro de puriy-costura y ya tenía dos consumidores), el `Provider` es `rimay-verbo-llama` (regla 10 — la familia verbo YA es la abstracción de embeddings, y éste es su primer backend sin nube ni descargas), y el índice es `rimay-verbo-index::VectorIndex`. Acá: la receta del modelo (multilingual-e5-small, MIT), que se pinea en fp32 y la receta CUANTIZA a Q8_0 con nuestro llama-quantize — así la procedencia es de quien declara la licencia, la imagen se lleva 126 MB en vez de 476, y la transformación es nuestra y verificable. Más el guardián y la extensión, que ahora expone `archive.ask` al lado del `archive.search` literal: son dos preguntas distintas y conviven. ⚠ El guardián está escrito pero NO corrió todavía: `ia-modelo-embeddings` quedó en la cola del flock detrás del build de otro agente. Lo medido hasta acá es el modelo a mano (384 dimensiones, el pasaje correcto gana con y sin los prefijos de e5) y 45 tests en tawasuyu. La línea del SDD que lo dice se borra cuando dé verde. |
||
|
|
d604d850db |
ADR 0017: kexec por kexec_file_load, y la decisión 2 pasa de viable a costo
La diferencia entre los dos syscalls decide el frente: kexec_load pide los segmentos armados y el purgatory (= kexec-tools entero); kexec_file_load recibe los descriptores y hace el trabajo adentro, ~50 líneas sin dependencias. Con recipes/linux-metal-kexec.toml (única diferencia: CONFIG_KEXEC_FILE) queda respondida media decisión abierta nº2: kexec y Secure Boot SÍ pueden coexistir, porque lockdown prohíbe kexec_load y acepta kexec_file_load con imagen firmada. Lo que queda es si se firma y quién paga el artefacto extra — decisión de coste, no de viabilidad. Y queda anotado el diagnóstico que mandaba al lugar opuesto: EPERM no es "falta CONFIG_KEXEC_FILE" sino falta de CAP_SYS_BOOT. Medido en el hub, cuyo kernel trae el flag y aun así dio EPERM por no ser root. Tercera vez en dos días que el error caro no es el mecanismo sino el mensaje que lo explica. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj |
||
|
|
0a9622c7b0 |
kernel kexec: el reboot suave por kexec_file_load, sin kexec-tools
ADR 0017 §1. Se usa kexec_file_load, no el kexec_load viejo, y la diferencia es de orden de magnitud: kexec_load recibe los segmentos YA armados y el purgatory —el código que corre entre los dos kernels—, o sea que usarlo obliga a reimplementar kexec-tools entero. kexec_file_load recibe los DESCRIPTORES del kernel y del initrd y hace el trabajo adentro. Son ~50 líneas. El syscall va con asm! en vez de agregar la crate libc al workspace: es UN syscall, su número y su ABI son contrato estable de Linux, y la alternativa era arrastrar una dependencia a un workspace que comparten dos frentes. El cmdline viaja CON su NUL: el kernel cuenta cmdline_len incluyendo el terminador, y sin él lee un byte de más. Es el error clásico de esta llamada. recipes/linux-metal-kexec.toml — variante cuya ÚNICA diferencia es CONFIG_KEXEC_FILE=y. Variante y no flag en la canónica porque el .config ES la identidad del artefacto (SDD 22 §1): tocar linux-metal re-hashea el kernel que arranca las imágenes y el que reproduce bit a bit, y el ADR deja esa decisión al usuario. Comprobado que la canónica no se mueve: sigue en b3:2ed8f54a…, el que ya está sellado. Y un diagnóstico que corregí a los dos minutos de escribirlo, porque mandaba al lugar OPUESTO: decía "falta CONFIG_KEXEC_FILE" para EPERM. Medido en este hub — el kernel de Artix trae CONFIG_KEXEC_FILE=y y aun así devolvió EPERM, por no ser root. EPERM es falta de CAP_SYS_BOOT (o lockdown si ya sos root); ENOSYS es el flag que falta. Confundirlos manda a recompilar un kernel que estaba bien. La ayuda del comando dice con todas las letras que esto NO es "actualizar sin rebootear": el userspace muere igual. Ahorra los 20-30 s de POST/UEFI, que es la mitad del downtime en un servidor remoto y nada en un portátil. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj |
||
|
|
f22beb1b35 | estado: cosecha granja 2026-09-12T01:03:10Z — avance del árbol KDE | ||
|
|
93d873c41d |
ADR 0017: la vuelta atrás del kernel, MEDIDA — y la hace el firmware
Cuatro arranques en OVMF con dos kernels REALES del store:
1º 6.16.12 estable → stage del 7.1.2: "copiado (17142784 bytes)",
"BootNext = Boot0004"
2º starting Boot0004 "takana (candidato)" → uname = 7.1.2,
"⏳ EN PRUEBA" → confirm → "✓ promovido"
3º arranque normal → uname = 7.1.2: el estable cambió
4º stage de un candidato ROTO (4K de basura) y reinicio →
starting Boot0002 (la ruta normal) → uname = 7.1.2 →
"✗ el candidato Boot0004 NO arrancó: esta sesión vino del estable"
La cuarta es la que justifica todo: la máquina sobrevivió a que le
instalaran un kernel que no arranca, sin que nadie interviniera, y
además SABE que pasó.
Y lo hizo sin código nuestro: BootNext es de un solo uso y el firmware
la consume al leerla, así que si el kernel muere el siguiente arranque
ya no la encuentra y cae en BootOrder. No hay contador que mantener ni
estado que se pueda corromper — el mecanismo ES el firmware.
Se suma la detección de entradas huérfanas en boot-status, que salió de
ver al banco de pruebas perder el estado en un tmpfs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
|
||
|
|
6642b93858 |
kernel A/B: BootNext hace la vuelta atrás, y la hace el firmware (ADR 0017 §2)
`takana kernel {stage,boot-status,confirm,rollback}`.
La idea que lo vuelve barato: hay DOS modos de fallo y sólo uno necesita
código nuestro.
1. El candidato NO ARRANCA (pánico temprano, EFI-stub rechazado). Lo
cubre BootNext y sale GRATIS: es una variable de UN SOLO USO que el
firmware CONSUME al leerla. Si el kernel muere, el siguiente
arranque ya no la encuentra, cae en BootOrder y vuelve al estable
sin que corra una línea nuestra. No hay contador que mantener ni
estado que se pueda corromper: el mecanismo es el firmware.
2. Arranca pero el sistema no queda sano. Eso el firmware no lo sabe ⇒
confirmación explícita, y cada arranque sin confirmar gasta un
intento.
El candidato va a una ranura PROPIA de la ESP, nunca encima del estable:
un fallo a media copia dejaría sin kernel al que volver. La copia es tmp
+ rename.
Y `stage` REPONE el BootOrder después de crear la entrada: reconcile
deja al candidato primero, y un candidato no debe volverse el default —
tiene que arrancar UNA vez. Para eso está BootNext.
Verificado con DOS KERNELES REALES del store, tres arranques en OVMF:
1º 6.16.12 estable, sin candidato → stage del 7.1.2
2º BdsDxe: starting Boot0004 "takana (candidato)" → uname = 7.1.2,
"⏳ EN PRUEBA", confirm → promovido (y la ruta fallback también)
3º arranque normal → uname = 7.1.2: el estable cambió
Un bug que cazó un test y no una revisión: `en_esp` normalizaba los
backslashes DESPUÉS de quitar la barra inicial, así que una ruta en
formato EFI salía como "/EFI/..." —absoluta— y Path::join con absoluta
DESCARTA la base: habría escrito en el /EFI de la raíz del sistema en
vez de dentro de la ESP.
Y un hallazgo del banco de pruebas que vale para el diseño: con el
estado A/B en un tmpfs, stage + reboot lo perdía y el sistema decía "sin
candidato". Falló SEGURO (no promovió nada), pero dejaba la entrada
NVRAM huérfana sin que nadie lo supiera. boot-status ahora lo detecta
preguntándole al firmware, que es la fuente de verdad, y lo dice.
88 tests del CLI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
|
||
|
|
13ce9b16f1 |
targets: la IA local entra en las CUATRO imágenes de escritorio
Decisión del usuario (2026-09-12): `llama-cpp` + `ia-modelo-chat` de raíz en escritorio-gnome, -kde, -cosmic y -sway. El motivo es que el panel de la barra lateral está en las cuatro —viene con `atuq`— y una función del navegador que sólo existe en una imagen es una trampa: en las otras el usuario ve el panel y lee «esta imagen no trae modelo». Cuesta ~1,25 GiB por imagen (199 M el motor + 1,04 GiB el modelo) y está escrito en el comentario, al lado de las líneas, para que bajarlo sea una decisión y no un descuido. Verificado como manda la lección de `foot` que este fichero ya aprendió: las dos raíces expanden en los cuatro perfiles (`scripts/targets.py`), las dos están SELLADAS al hash vigente, y el grafo las ve con sus cuatro perfiles encima — una raíz que no resuelve quedaría `wanted` y ninguna métrica lo diría. El grafo CIERRA y el topo-sort sigue OK. |
||
|
|
af20d90648 |
bzip2: cuatro comandos rotos desde siempre — /usr/bin/bzcmp -> /out/usr/bin/bzdiff
`bzcmp`, `bzegrep`, `bzfgrep` y `bzless` son symlinks con **la ruta del SANDBOX horneada dentro**. El Makefile de bzip2 los crea con `ln -s $(PREFIX)/bin/bzdiff bzcmp` —con `$(PREFIX)` DENTRO del destino— y como bzip2 **no soporta `DESTDIR`**, la receta pasa `PREFIX=/out/usr` (el mismo truco que valkey) ⇒ el destino que queda es `/out/usr/bin/bzdiff`. Al hidratar, esos cuatro apuntan a un directorio que en el sistema real no existe. ⚠ **Cinco indicadores en verde sobre cuatro comandos que no funcionan**: el artefacto tiene contenido, `bzip2`/`bzgrep`/`bzdiff` corren, `static-audit` pasa, el grafo lo cuenta y la receta REPRODUCE. Ninguna métrica existente mira a dónde apunta un enlace. Arreglo: rehacerlos RELATIVOS después del install, que es lo correcto para un artefacto direccionado por hash — un enlace relativo sigue valiendo esté el árbol montado donde esté. Verificado: los cuatro resuelven y `bzip2 -c | bzcat` sigue dando `hola`. ## El guardián, en el mismo sitio y por la misma pregunta `--auditar-raices` ya contestaba «¿esta receta dejó ficheros donde no van?». Ahora contesta también la otra mitad: «¿dejó ENLACES a una raíz que no es del FHS?». Son la misma familia —un `install` que se equivocó de destino— y comparten la definición de `FHS_RAIZ`, que es lo que evita dos listas que se desincronizan. La regla es independiente del perfil, y por eso vale sobre el store entero: un enlace a OTRO artefacto es normal (`kinfocenter -> /usr/bin/systemsettings`, los dos en `escritorio-kde`, resuelve en el rootfs fundido). Lo que nunca puede estar bien es un destino absoluto cuya primera componente no sea del FHS: `/out`, `/src`, `/tmp` son rutas del lab. Dos correcciones al propio guardián, las dos aprendidas midiendo: · **Sólo audita el artefacto VIGENTE de cada receta.** El store guarda todos los sellados históricos, así que la primera versión acusaba a bzip2 por el artefacto YA SUPERADO — el mismo sobre-reporte que `static-audit.sh` tuvo que quitarse de encima. El hash vigente sale de los grafos de estado, no de 1150 llamadas a `takana hash`. ⚠ Y hay que regenerar **los cinco** grafos: con sólo el principal regenerado, el de KDE seguía apuntando al bzip2 viejo y el filtro lo dejaba pasar. · **Un barrido que no miró NADA lo dice y sale 2.** Filtrar por hash vigente hace que un `--store` que no case con los grafos deje cero artefactos auditados, y sin la guarda eso se imprimía como «✓ 0 ofensores»: una respuesta falsa con forma de respuesta. Probado: `--store /tmp` → exit 2. El control del hallazgo es el propio arreglo, sobre datos reales y no sintéticos: antes del fix el barrido nombra los cuatro enlaces de bzip2; después, `✓ ninguno`. |
||
|
|
0f58df4795 |
planear: el preflight medía contra / — y los datos de gioser no entran ahí
La cadena `censar → planear → aplicar` corrió entera contra gioser por primera vez: 59 pasos, 20
ejecutables y 39 manuales, bien separados. El ensayo en seco destapó lo que ninguna prueba de juguete
iba a mostrar: **33,6 G de datos contra una raíz con 3,5 G libres** (el sitio está en `/work`, 66 G).
Dos cosas estaban mal a la vez:
· **El plan copiaba ruta → MISMA ruta**, sin forma de decir dónde cae cada árbol en el destino. Ahora
`[[datos]]` tiene `destino` (vacío = la misma ruta). El fallo que evita es caro: aparecía a mitad
de un rsync de 22 G.
· **El preflight sumaba todo y lo comparaba contra `/`.** Acierta POR CASUALIDAD mientras todo caiga
en `/`, y da un veredicto completamente falso en cuanto una ruta va a otro montaje — en las dos
direcciones. Ahora mide por sistema de ficheros, agrupa los destinos, nombra qué rutas caen en cada
uno, y el veredicto lo saca el propio comando en vez de un humano leyendo una columna.
Los dos controles, contra la caja de verdad:
✗ / necesita 34386 MiB · libres 3596 ⇐ /var/www /var/lib /srv /opt /home exit 1
✓ /work necesita 34386 MiB · libres 66192 ⇐ /work/var/www … /work/home exit 0
Además, el paso `muere` ahora cumple la regla 2 ENTERA («por su nombre Y CON SU TAMAÑO»). Un dominio
fósil no pesa nada por sí mismo: pesa lo que dejó en disco, y `terapeuta.ec` —que no resuelve en
ningún DNS— tiene 279 M en `/var/www/terapeuta`. Que eso se supiera dependía de que alguien se
acordara. Se lista un renglón POR DIRECTORIO (siete dominios `*.gioser.net` reclaman el mismo
`gioser.bak`; repetirlo siete veces convierte el aviso en ruido) y se dice «podría ser de», nunca
«es»: el directorio se llama `terapeuta` y el dominio `terapeuta.ec`, y emparejar «casi» acierta casi
siempre y el resto de las veces manda a borrar lo que no era. Los directorios que no reclama nadie se
listan aparte: son los que nadie recuerda.
⚠ Y un bug que me hice yo y que el control cazó: empalmar por `str.index('# ── 1. datos ───')` cuando
ese marcador existe en DOS funciones. Tomó el corte al revés (j < i) y DUPLICÓ `def pasos` entero;
Python se queda con la última definición, así que el fichero importaba bien y generaba planes con el
preflight viejo. Se vio porque el comando del plan no era el que yo acababa de escribir. Un marcador
de empalme que no es único no es un marcador.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
2b87ecb50f |
respaldo del arranque: lista blanca, porque BootCurrent rompía la idempotencia
Dos arranques reales seguidos, sin tocar nada, daban dos respaldos con nombre distinto: boot-27b952c9….json y boot-f999e1e8….json. La promesa de "un arranque que no cambió produce el mismo fichero" era falsa. La causa: el filtro era «empieza por Boot», y eso deja entrar BootCurrent, que dice por dónde arrancó ESTA vez y cambia en cada arranque. Y encima es de SÓLO LECTURA, así que restore habría intentado escribirla. Ahora es lista blanca —BootOrder y Boot#### y nada más—, que también deja fuera BootNext (de un solo uso: restaurarla dispararía un arranque que nadie pidió) y BootOptionSupport (informativa). Ninguna de las tres describe cómo debe arrancar la máquina. Comprobado: cambiando BootCurrent a mano, el respaldo da el mismo fichero y lo dice — "idéntico a uno que ya estaba: el arranque no cambió". Esto sólo se veía ARRANCANDO DOS VECES. Un test del respaldo contra un efivarfs fabricado habría pasado en verde: la variable que rompía la propiedad la pone el firmware, no el código. 80/80 del CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj |
||
|
|
bd1fa8bf8a |
boot entry backup/restore: el arranque como estado reproducible (ADR 0018 §5)
Guarda las variables de NVRAM CRUDAS —se reescriben tal cual; decodificarlas para volver a codificarlas al restaurar sería una oportunidad de perder algo que no entendemos— y de la ESP un manifiesto con hashes, no los bytes: el kernel ya vive en el store y copiarlo otra vez sería churn. El manifiesto es evidencia de qué había; para lo que no esté en el store dice qué falta, en vez de prometer reponerlo. El ADR se equivocaba en DÓNDE: decía "volcar al store". No va al store, porque lo barre store-gc.sh, que clasifica por nombre de receta — un respaldo ahí sería huérfano y se borraría en el primer --huerfanos. Vive en /var/lib/hammer/boot/, que en las imágenes es su propia partición. El nombre sale del CONTENIDO, así que un arranque que no cambió produce el mismo fichero: corre en cada arranque sin llenar la partición de copias. Lo que encontró la prueba de punta a punta y no estaba en el diseño: restaurar es volver al pasado, y lo que llegó DESPUÉS no estaba en ese pasado. Al reponer el BootOrder del respaldo, el Windows instalado más tarde quedaba FUERA del orden — correcto, y justo lo que el usuario no espera de algo llamado "restaurar el arranque". Ahora se avisa antes, con nombre y apellido, y restore NO escribe por defecto: la NVRAM es lo único de la máquina que no se rehace desde el store. El lector FAT ganó lectura de ficheros, con su trampa propia: hay que truncar al tamaño DECLARADO en el directorio, no al final del último cluster. Un fichero de 1,5 MB en clusters de 1 KiB termina con relleno y hashear el relleno daría un hash distinto al del mismo fichero en disco — el síntoma sería "dos respaldos del mismo arranque difieren". Verificado contra mtools como oráculo (1 500 000 y 900 000 bytes exactos, mismo sha256 que los originales) y con un test determinista que fabrica una FAT16 a mano, sin depender de que mtools esté. 79/79 del CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj |
||
|
|
61b57ab060 | estado: cosecha granja 2026-09-12T00:31:34Z — avance del árbol KDE | ||
|
|
d0e3aadb12 |
declarar: /proc pasa a ser un lector del centro — y había DOS emisores de tarjetas, mal los dos
`declarar.py` tenía su propio molde de card de arje y `formatos/arje.py` el suyo: el N×M que el
pivote existe para evitar, adentro de mi propio código. Y no quedó en teoría — CADA COPIA TENÍA UN
CAMPO MAL DE LA RAÍZ, Y NINGUNO DE LOS DOS EL MISMO:
campo declarar.py arje.py semilla REAL del producto
provides ["Spawn","Journal"] ✓ [] ✗ ["Spawn","Journal"]
supervision Restart{…} ✗ "OneShot" ✓ "OneShot"
Las consecuencias son concretas: sin `Spawn`/`Journal` las hijas no tienen quién las lance ni dónde
escribir, y una card `Virtual` con `Restart` le pide a arje que respawnee algo que nunca corrió.
Ahora hay un lector `proc` (el censo es un formato de origen, igual que systemd u OpenRC) y el
escritor `arje` es el ÚNICO que emite tarjetas; `declarar.py` queda con lo suyo, el perfil. Tres
lectores en la familia: systemd y openrc leen LO DECLARADO —que es lo que miente, `rc-status` daba
`stopped` para cinco servicios vivos— y `proc` lee LO QUE CORRE, que es donde aparecen los 15
`no-declarado`.
Dos cosas más, las dos sobre no mentir:
· **Una semilla vacía parecería un éxito.** Si ningún servicio tiene `decision = "muda"`, el lector
lo DICE y sale ≠0 en vez de emitir cero tarjetas en silencio. Mismo modo de fallo que un artefacto
vacío en el store.
· **El acta se ahogaba en su propio ruido.** Anotaba el `envp` vacío una vez POR SERVICIO: 26 líneas
idénticas que tapaban los dos hallazgos reales (`shuma-daemon` corre como `sergio`; los que no
tienen cmdline). La limitación es del lector y vale para todos ⇒ una entrada nombrando a los 26.
Un acta donde casi todo es la misma línea se deja de leer, y entonces no queda ningún acta. Y al
revés: las entradas que SÍ son por servicio ahora lo nombran (`gitea: cwd=/var/lib/gitea`).
Controles: la raíz generada coincide campo por campo con la del producto; dos corridas dan el fichero
byte a byte idéntico; 26 tarjetas con 26 ids únicos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
0e5e3f8c83 | estado: cosecha granja 2026-09-12T00:01:52Z — avance del árbol KDE | ||
|
|
e1413d5e8d |
qdrant: promovida al corpus — limpia, reproducible, y la familia «base vectorial» deja de estar vacía
El worker selló la versión con el `install` que limpia `/out/src`: **70 M en vez de 139**, sólo
`usr/bin/qdrant`, sin NEEDED. Y REPRODUCE bit a bit (verificado allá, donde vive el artefacto).
corpus 890/890 sellado · deuda 0 · el grafo CIERRA
Control de la promoción, en los dos sentidos: el hash antes y después del `git mv` es el mismo
(`4bd8feca…`) — la ruta no entra en `hash_inputs`, así que mover de cola al corpus no re-hashea nada.
De las cuatro familias que la tabla de `planear.py` daba vacías al empezar la noche quedan sólo
«contenedores», y eso es HONESTO aunque `crun` ya esté sellado: crun es el runtime OCI, la capa de
abajo — no sustituye a docker/podman/containerd, los ejecuta. Meterlo en esa casilla sería declarar
resuelta una decisión que sigue abierta.
El artefacto vive en el store del worker y el hub lo cuenta por manifiesto, que es como está
diseñado desde que el store se mudó al volumen.
|
||
|
|
43a7ef0bd1 |
static-audit: auditaba SÓLO recipes/ y remataba con «toda receta lo cumple» — las 5 colas nunca se miraron
El audit del enlace estático globeaba `recipes/*.toml` y nada más, y cerraba con
«✅ toda receta que declara link=static lo cumple»: una frase verdadera de una PARTE del corpus,
presentada como si fuera de todo él. Las cinco colas (`incoming{,-kde,-gnome,-cosmic,-wlr}`) son
~305 recetas que **nunca se auditaron**.
Lo destapé por accidente: al promover `libseccomp` de `incoming-gnome/` a `recipes/`, **el artefacto
no cambió ni un byte** y el audit pasó de MIENTEN:0 a MIENTEN:1. La mentira estaba ahí desde siempre
y lo único que la tapaba era el glob de una línea. Un guardián que mide menos de lo que su resumen
afirma es peor que no tenerlo: da por cubierto lo que no mira.
Ampliado el barrido a las colas, aparecen **3** que llevaban invisibles:
libseccomp scmp_sys_resolver 6 rebuilds (corpus 1 + incoming-gnome 5)
libgcrypt dumpsexp y 2 más 41 rebuilds (corpus 1 + incoming-kde 40)
libgpg-error gpg-error 45 rebuilds (corpus 4 + incoming-kde 41)
Radios medidos con `yupana radio`, que cruza colas — no con `grep recipes/*.toml`, que es justo el
error que este commit arregla.
En las tres, lo dinámico es un **binario auxiliar de diagnóstico**, no la librería: los consumidores
enlazan el `.a`, así que el impacto funcional hoy es NULO. Lo que está mal es la declaración, y
arreglarla cuesta ~92 rebuilds casi todos de KDE. Van con el próximo bump de cada una, cuando el
re-hash ya esté pagado — mismo criterio que la deuda de `perl`. Decisión con el número delante, no
olvido.
Por eso entran en `DEUDA_DECIDIDA` y se imprimen como `•` sin hacer fallar: si el audit fallara
siempre por tres viejas, un ofensor NUEVO se perdería entre el ruido. El resumen ahora distingue
«MIENTEN (nuevos)» de «deuda decidida».
Probado con los dos controles y no sólo con el que da verde: quitándole a propósito el
`LDFLAGS=-all-static` a `crun` y reconstruyéndola, el audit sale **1** y la nombra; restaurada, sale
**0**. O sea que la tabla de deuda no se traga a un ofensor nuevo. El artefacto de la prueba se
borró del store.
|
||
|
|
196c056834 | estado: cosecha granja 2026-09-11T23:02:23Z — avance del árbol KDE | ||
|
|
2fa89ce3fa |
censar: tres nombres equivocados que llegaban al plan, y los 7 «binario desconocido» eran permisos
El censo nombraba los servicios por `comm`, que viene del kernel. De ahí salieron tres errores de
CLASIFICACIÓN — y no son cosméticos: ese nombre es el que el plan mete en `--in /etc/init.d/<n>` y el
que va de `label` en la tarjeta de arje.
· `comm` está capado a 15 caracteres: `willay-crosscheck` llegaba como `willay-crossche` y con ese
nombre no casaba contra su declaración ⇒ figuraba como `no-declarado` TENIENDO su tarjeta en la
semilla de arje. La línea de comando trae el nombre entero.
· `supervise-daemon` no es un servicio, es un ENVOLTORIO. Los cinco de gioser se fundían en una
entrada `supervise-daemo`; y del otro lado `dbus`, `metalog`, `dhcpcd`, `squid` y `shuma-daemon`
salían como `declarado-muerto` ESTANDO VIVOS — el mismo agujero que este censo existe para tapar,
entrando por la otra puerta. El nombre real es su argv[1] y el comando real es el último token
antes del `--` suelto (comprobado contra los cinco).
· `head -15` con ppid==1 era un resto de tubería reparentado, contado como servicio. Va a
`descartados`, que se imprimen: un huérfano es un hallazgo, no basura.
**Y los 7 «no se pudo leer su binario» eran dos cosas distintas.** Muchos demonios reescriben su
`argv[0]` (`sshd: /usr/bin/sshd [listener]`, `php-fpm: master process (…)`), así que la línea de
comando no dice cuál es el binario — `/proc/<pid>/exe` sí, y necesita ser dueño o root. Medido:
uid 1001 : desconocido 7 · paquete-ajeno 13 · receta-takana 6 · suelto 13
root : desconocido 0 · paquete-ajeno 20 · receta-takana 5 · suelto 14
Ahora el censo DICE cuál de las dos pasó: «repetilo con sudo» y «averigualo a mano» son trabajos muy
distintos, y confundirlos manda a alguien a investigar un permiso.
Desenvolver al supervisor arregló además dos datos que salían falsos: el `exec` de `squid` era
`/usr/bin/supervise-daemon` (⇒ figuraba provisto por el paquete `openrc`), y el `cmdline` guardado
era el del SUPERVISOR — una tarjeta hecha con eso arrancaría `supervise-daemon` dentro de arje, que
ya supervisa. Y se rescata el `--user`: `shuma-daemon` corre como `sergio`, dato que la tarjeta de
arje no puede guardar (no tiene campo de usuario), así que ahora se avisa EN LA REVISIÓN — sin eso a
la vista, un servicio que acá corre sin privilegios termina de root en el destino.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
2201fac08e |
boot entry survey: el informe del terreno, leyendo la ESP SIN montarla (ADR 0018 §4)
Reporta firmware, discos, ESPs, con quién se comparten y si la
instalación entra. No escribe nada — y para poder prometer eso hubo que
leer la ESP sin montarla, que es lo que obligó al lector FAT de sólo
lectura (fat_ro.rs). Montar para averiguarlo pedía privilegios, dejaba
un efecto secundario justo cuando prometimos no tocar nada, y falla si
el vecino dejó la FAT sucia por su hibernación.
Sobre una ESP de fábrica (100 MiB) con Windows dentro:
FAT32 «ESP» — 100.0 MiB totales, 77.1 MiB usados, 22.1 MiB libres
vecinos en \EFI: Microsoft, BOOT
⚠ «Microsoft» ⇒ hay Windows en esta ESP. Es el que reordena
BootOrder al actualizarse.
✗ NO ENTRA: hacen falta 31.0 MiB y hay 22.1 MiB libres
Contrastado contra mtools como oráculo, que es lo que hace creíble el
número: mdir dice "23 221 248 bytes free" y el lector propio dice
22.1 MiB — el mismo. Los clusters libres se cuentan recorriendo la FAT y
NO se lee el FSInfo de FAT32 a propósito: ese campo lo deja
desactualizado un SO que desmontó mal, y un número optimista de más
haría fallar la instalación a mitad — justo lo que el §4 evita.
En el instalador el §4 resultó ser algo más que "cuánto espacio hay": la
rama UEFI se lleva el disco ENTERO, así que lo que hay que decir en voz
alta es con qué se lo va a llevar puesto. El survey corre antes de
particionar y nombra el \EFI\Microsoft si está. El usuario se entera
ANTES, y no después de que su Windows dejó de arrancar.
Dos cosas que habrían pasado inadvertidas sin test: el nombre largo
tiene que ganarle al 8.3 (sin juntar los LFN, "Microsoft" se lee
"MICROS~1" y la advertencia no dispara nunca), y el tipo de FAT sale del
número de clusters y no del texto del BPB, que es informativo y hay
formateadores que mienten. El primer test del tipo lo escribí mal —1999
clusters ES FAT12— y el síntoma es idéntico a un bug del lector.
78/78 del CLI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
|
||
|
|
beb1c2a597 |
auditar-raices: el guardián de la raíz sucia sólo veía los perfiles — y hay 649 recetas fuera
`hydrate-profile.py` ya tenía el bloque «quién ensucia la RAÍZ del rootfs», y es bueno: mira por ARTEFACTO y no sobre el árbol fundido, porque en el fundido el nombre del culpable ya se perdió. Pero sólo corre al hidratar un perfil ⇒ **sólo ve lo que alguna imagen declara**. Hoy hay **649 recetas que no alcanza ninguna imagen**, y una fase `install` que se equivoca de destino en una de ésas es invisible hasta el día que alguien la declare. Lo destapó `qdrant`: selló con **69 M** de cabeceras de protobuf bajo `/src`, y no está en ningún perfil ⇒ ningún guardián lo habría visto. Lo encontré mirando el árbol del artefacto a mano antes de promoverlo, que es justo lo que no se puede dejar a que alguien se acuerde. `--auditar-raices` hace la misma pregunta sobre el STORE ENTERO sin hidratar nada, reutilizando el mismo `FHS_RAIZ` (una sola definición de «qué puede ir en la raíz», no dos que se desincronizan). Y dos tablas, porque un barrido que canta tres cosas de las cuales dos son correctas se deja de leer: · `RAIZ_POR_CONTRATO` — `seed-zig` (el toolchain ES el artefacto) y los rootfs (`store`/`ente` son suyos por diseño). Se imprimen como ⊘ con el motivo y no cuentan. · `RAIZ_DEUDA_DECIDIDA` — `perl` y sus 945 páginas nroff en `/`. Es suciedad REAL pero su arreglo está decidido EN CONTRA por ahora (una línea, 305 rebuilds, va con el próximo bump). Se imprime como contexto y **no hace fallar**: si fallara siempre, un ofensor NUEVO se perdería entre el ruido del viejo — que es exactamente cómo se muere un guardián. Probado con los dos controles, no sólo con el que da verde: sobre un store de juguete con una suciedad inventada sale **1** y la nombra; quitándola sale **0**. Sobre el store real: 0 ofensores nuevos sobre 3 artefactos conocidos. |
||
|
|
54bf3e810e |
ia: el modelo de chat, pineado — Qwen2.5-1.5B-Instruct Q4_K_M (Apache-2.0)
Elegido por tres cosas, y las tres medidas antes de pinearlo: licencia Apache-2.0 (lo que una distro puede shipear sin letra chica, al revés que Llama-3.2 o Gemma), habla español —se le preguntó qué es una distribución de GNU/Linux y contestó dos frases correctas— y corre en CPU: 20,1 tokens/s en el hub sin GPU, 1,04 GiB. El objeto pineado es un tar que envuelve el .gguf, publicado en el mirror y servido por sha256: takana extrae toda fuente con `tar` y un GGUF pelado no lo es. Es el camino de firefox-pgo-profile. La receta anota el sha256 del GGUF DE UPSTREAM (el lfs.oid de HuggingFace, verificado al bajarlo) y no sólo el del tar nuestro, para que nadie tenga que confiar en nuestro tar. Round-trip verificado: apartados el caché y el artefacto, el build lo bajó del mirror y selló el mismo ArtifactHash. Guardián en la receta, porque el fallo es callado: un fichero truncado o un HTML de error renombrado a .gguf se instala igual y sella en verde. Se comprueban el mágico GGUF y el tamaño. ⚠ Y el modelo de verdad destapó una CARRERA en el guardián del §6.7: el censo de motores contaba en el instante del cierre — con el de juguete daba 0 y con el de la imagen daba 1, que se lee como fuga cuando en realidad matar un proceso con un giga mapeado tarda ~1 s. Ahora espera hasta 15 s y anota cuánto tardó. La rotura a propósito sigue fallando, ahora con el tiempo a la vista. Falta decidir en qué imágenes se declara (con el motor son ~1,25 GiB por perfil) y pinear el de embeddings (multilingual-e5-small) para la mitad semántica del §6.3. |
||
|
|
3038195ea7 | estado: cosecha granja 2026-09-11T22:31:56Z — avance del árbol KDE | ||
|
|
66eaf8738f |
el reconciliador ARRANCA solo: montar sysfs, y un diagnóstico que no mentía a medias
Cierra el §2 del ADR 0018, verificado con dos arranques de la imagen
completa en OVMF:
1º (por la fallback) → "ESP detectada en /dev/vda p1",
"⚠ EL ARRANQUE ESTABA CAMBIADO — takana lo repuso",
"faltaba la entrada «takana» ⇒ escrita en Boot0004",
"BootOrder: 0000,0001,0002,0003 → 0004,0000,0001,0002,0003"
2º → BdsDxe: starting Boot0004 "takana" from HD(1,GPT,923A070F-…)
/\EFI\takana\takanax64.efi
"✓ arranque en orden: Boot0004 «takana» ya es la primera"
Un sistema recién instalado se da de alta en el firmware en su PRIMER
arranque y desde el segundo arranca por su propia entrada, sin que nadie
corra un comando. Y el segundo no escribe nada.
Lo caro no fue el reconciliador sino un diagnóstico FALSO: el primer
arranque con el hook dijo "sin firmware EFI — esta máquina no arrancó
por UEFI" en una VM que SÍ arrancó por UEFI. La causa no tenía nada que
ver con UEFI: busybox switch_root no arrastra /sys —igual que no
arrastra /dev, cosa que el script ya contemplaba— así que el directorio
de efivars no existía. El mensaje mandaba a investigar el firmware, que
estaba perfecto.
Dos arreglos:
- el wrapper monta sysfs y después efivarfs. CONFIG_EFIVAR_FS=y ya
estaba en el .config SELLADO (verificado, no supuesto) ⇒ no se
re-hashea ningún kernel.
- el mensaje distingue TRES estados que se parecen: no existe (BIOS o
/sys sin montar), existe y está VACÍO (falta el mount, y lo dice con
el comando exacto), o tiene variables. Decir "no hay UEFI" cuando
falta un mount manda a diagnosticar al lugar equivocado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
|
||
|
|
236abe48f3 |
boot entry reconcile: converger con la NVRAM en CADA arranque (ADR 0018 §2)
La NVRAM es estado compartido y el vecino la reescribe sin coordinarse. Contra eso no sirve confiar: sirve converger. `reconcile` descubre su propia ESP, repone la entrada y el BootOrder, y es idempotente. Descubre la ESP por TIPO de partición (GUID de ESP en GPT, 0xEF en MBR), que es el único dato fiable sin montar nada — un reconciliador que monta sistemas de ficheros en el arranque es un efecto secundario que no queremos. Si hay VARIAS ESP no adivina: las lista y pide --disk. Elegir mal significa escribir una entrada que apunta a un disco que puede no estar, y eso es peor que no escribir nada. No rompe el arranque por nada: sin firmware EFI (máquina por BIOS) lo dice y sale 0. Y cuando actúa, GRITA — va a /dev/tty0 además del serial, a diferencia del menú de arranque. Un reconciliador que repara en silencio deja al usuario conviviendo con una rareza intermitente que no entiende; el mensaje dice explícitamente que si se repite en cada arranque es que otro sistema operativo le está reescribiendo la NVRAM. Enganchado al wrapper de PID1 de las dos imágenes, con el mismo timeout y el mismo || true que el menú: corre ANTES del exec de arje-zero y colgarse ahí es un arranque muerto e indistinguible de un kernel colgado. El test que vale es el escenario completo: takana se instala, llega el vecino y se pone primero, y el siguiente arranque lo repone — SIN borrar la entrada del vecino (takana se pone primera, no lo echa) — y el arranque siguiente ya no escribe nada. Más el GUID de ESP, que va en orden DE DISCO y no en el legible: escribirlo "como se lee" es el error clásico y no casaría con ninguna ESP real. 15 tests en el módulo, 74/74 del CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj |
||
|
|
733065b1c4 |
qdrant: CONSTRUYE y ARRANCA — pero el artefacto venía con 69 M de basura, y la causa es del lab
Selló en el worker con el arreglo del `compiler = "gcc"`. Verificado allá, corriéndolo y no por el
código de salida: binario estático de 70 M sin NEEDED, `qdrant --version` → `qdrant 1.19.1`, y
levantándolo de verdad:
Qdrant HTTP listening on 6399
Qdrant gRPC listening on 6334
Access web UI at http://localhost:6399/dashboard
⚠ **Y al mirar el ÁRBOL del artefacto antes de promoverlo, pesaba 139 M — la mitad, basura.** 140
ficheros bajo `src/target/release/build/protobuf-src-*/out/install/include/google/protobuf/…`: las
cabeceras y libs del protobuf que `protobuf-src` compila para su uso interno.
**La causa no es de esta receta, y por eso vale escribirla.** El sandbox exporta **`DESTDIR=/out` de
forma GLOBAL** (`sandbox.rs`), para que el `make install` de las recetas autotools funcione.
`protobuf-src` hace su propio `make install` DENTRO de la fase compile, con
`--prefix=/src/target/release/build/…/out/install`, y ese install anidado **hereda el DESTDIR** ⇒ su
prefijo aterriza en `/out/src/target/…`. Nadie lo pidió, nada falla, y el artefacto sella con el
doble de tamaño y un `/src` en la raíz que al hidratar se proyectaría sobre el FHS de la imagen.
Le puede pasar a cualquier receta cuyo build ejecute un `make install` anidado — los crates `*-src`
son la familia entera. Se limpia en la receta y NO en el lab: quitar el `DESTDIR` global cambiaría el
comportamiento de todas las recetas autotools del corpus, que es una campaña con su propia
verificación. `/out/src` nunca es salida legítima — la raíz del artefacto es un FHS y `/src` es el
nombre del bind del lab.
Queda en `incoming/` hasta que el worker selle la versión limpia; promover a `recipes/` con el
artefacto sucio sería meter los 69 M al grafo.
|
||
|
|
3064798fe5 | estado: cosecha granja 2026-09-11T21:31:43Z — avance del árbol KDE | ||
|
|
2f0d5654e8 |
ADR 0018: el arranque por vendor path, MEDIDO — el seguro se cobra
Segunda enmienda, y cierra la primera del mismo día.
El experimento: la MISMA imagen con la ruta fallback pisada con 4K de
basura, dos corridas en OVMF que difieren SÓLO en la NVRAM.
control (NVRAM limpia) → "No bootable option or device was found",
cero líneas del initramfs
prueba (con la entrada
que escribió takana) → BdsDxe: starting Boot0004 "takana"
from HD(1,GPT,DA80800E-…,0x800,0x30000)/\EFI\takana\takanax64.efi
El firmware imprime el device path que decodificó: 0x800 = LBA 2048 y
0x30000 = 196608 sectores, exactamente los números que takana leyó del
GPT. No es que "arrancó": arrancó POR la entrada que escribimos, con la
fallback destruida. Es el escenario de Windows con el seguro cobrado.
La idempotencia del §2 quedó probada en condiciones reales y no en un
test: la segunda corrida dice "ya dice exactamente esto — no se
reescribe" y "BootOrder ya empieza por Boot0004 — no se toca". La NVRAM
tiene un número finito de escrituras y esto corre en cada arranque.
El parser se validó además contra las CUATRO entradas reales del
firmware (BootManagerMenuApp, EFI Firmware Setup, UEFI Misc Device, EFI
Internal Shell): un vector que nadie escribió para que pasara.
Y la dependencia efibootmgr se retira: no hizo falta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
|
||
|
|
ba5754551f |
takana boot entry: la entrada NVRAM, sin efibootmgr — con el muro medido
ADR 0018 §1. El ADR dejaba dos vías y se intentó primero la ajena:
efivar 38 con musl/zig-cc choca con secure_getenv (no existe en musl),
sys/cdefs.h (tampoco) y -Wl,--add-needed (lld lo rechaza) — tres muros
sólo para compilar su generador de tablas, y arrastraría popt al sistema
instalado. Es el patrón "pantano de parches de distro".
Lo que efibootmgr hace en el fondo es escribir DOS ficheros, Boot#### y
BootOrder, con una estructura de la spec UEFI estable desde 2.0. Hacerlo
acá es menos código que mantener los parches, no agrega dependencias al
sistema instalado y el binario takana ya está en el disco.
crates/takana-cli/src/efi_boot.rs — EFI_LOAD_OPTION + device path
(HARD_DRIVE/FILE_PATH/END), lectura y escritura de efivarfs, y un lector
de tabla de particiones que detecta GPT o MBR solo. Los dos casos hacen
falta: install-image-efi.sh fabrica GPT y takana-live-install.sh instala
sobre MBR.
`takana boot entry {list,add}` (CLI en inglés, regla 4). `add` es
IDEMPOTENTE a propósito: está pensado para correr en CADA arranque como
reconciliador (§2), y la NVRAM tiene un número finito de escrituras, así
que si ya dice exactamente eso no se reescribe.
parse_load_option existe para poder COMPROBAR al constructor: un device
path mal formado no da error, el firmware ignora la entrada en silencio
y el usuario ve "no arranca" sin una sola pista.
10 tests, con los negativos que son los que valen: un disco sin 0x55AA
falla en vez de devolver ceros, una entrada GPT vacía no se convierte en
partición de tamaño 0, y un FilePathListLength que miente es error y no
un truncado silencioso. 69/69 del CLI en verde.
Verificado además contra una tabla GPT REAL de sfdisk: inicio LBA 2048 y
131072 sectores, los mismos números que reporta sfdisk -l.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
|
||
|
|
2ecce583e5 |
crun: un contenedor ARRANCÓ de verdad — la hoja de la última familia vacía
`crun` 1.29.1, y la prueba no es `--version`: con el `busybox` del propio corpus como rootfs y un
bundle OCI mínimo, rootless,
$ crun run prueba-takana
HOLA-DESDE-EL-CONTENEDOR
Linux
0
93
Entra como HOJA a propósito de la familia «contenedores», la última que la tabla de `planear.py`
daba vacía. Un runtime OCI es lo que cualquiera de los tres candidatos (docker, podman, containerd)
acaba ejecutando por debajo, así que esto **no presupone cuál se elige arriba** — esa decisión sigue
abierta y no la toma una receta.
crun y no runc: C en vez de Go, ~300 KB contra ~10 MB, y es el runtime por defecto de Alpine ⇒ musl
es objetivo probado. No son excluyentes.
Tres deps que NO se deducen del proyecto, y las tres abortaban el `configure`:
· `--disable-systemd` no es preferencia: `AC_CHECK_HEADERS([systemd/sd-bus.h], [], [AC_MSG_ERROR…])`
lo hace OBLIGATORIO salvo que se apague. Esta distro no lleva systemd ⇒ el cgroup manager será
`cgroupfs`. Coherente con el resto del sistema, y mejor decidido acá que descubierto después.
· **`argp-standalone`**: crun parsea flags con `argp_parse(3)`, extensión de glibc que musl no tiene.
La receta YA estaba en el corpus y sellada — sólo faltaba que alguien la pidiera.
· **`python3`** aunque crun sea C puro: su `AM_PATH_PYTHON` no está marcado opcional. Misma figura
que meson.
**Y el vigía de enlace estático se ganó el sueldo.** El primer sellado pasó todo —corría, 0 deuda—
pero `static-audit.sh` cantó: `✗ crun dice static, es DINÁMICO → libc.so`. crun enlaza con libtool,
que lee el `-static` del lab como «preferí los `.a` de libtool» y no como flag al linker. El arreglo
es `LDFLAGS="-all-static -no-pie"` en compile **y en install** — libtool RELINKEA al instalar, así
que sólo en compile deja bueno el árbol de build y dinámico lo que se sella. Ahora: 0 NEEDED, y
`+CAP +SECCOMP +EBPF +JSON_C` intactos.
libcap y libseccomp se declaran y NO se apagan, que es la decisión contraria a los `--disable-*` de
arriba y es deliberada: son las dos piezas con las que un runtime OCI acota de verdad al contenedor.
Un crun sin ellas compila igual, aísla mucho menos, y el binario no lo diría.
**Promoción**: `libseccomp` pasa de `recipes/incoming-gnome/` al corpus, porque ya tiene dos
consumidores independientes (el stack de GNOME y crun) y no hay variante homónima con la que colisionar.
Control en los dos sentidos antes de moverla: su hash y los de `gnome-shell`/`mutter` son idénticos
antes y después, y coinciden con los que el grafo ya tenía registrados ⇒ **cero re-hasheo**. La
resolución sibling→padre de `resolve_dep_path` hace que los consumidores de la cola la sigan viendo.
|
||
|
|
73f75d45ed |
censar: sonda DNS-only (--probe-dns) — y los dominios fósiles vuelven a la lista de decisiones
Censo real de gioser (204.168.193.248, identificado por IP y no por hostname, que dice «momento»): 19 vivos, 18 NO-DECLARADOS, 85 declarados-muertos, 29 dominios. **El HTTP toca al origen; el DNS no.** Sondear los dominios desde la propia máquina disparó su fail2ban y la dejó incomunicada, así que el censo en local los salteaba — y quedaban 29 de 66 ítems SIN recomendación por una precaución correcta aplicada de más. Lo que dispara la jaula es el HTTP: `getent` le pregunta al DNS, no al servidor. `--probe-dns` sondea sólo el nombre, sin UNA SOLA petición a gioser, y contesta la pregunta que más pesa en una mudanza: cuáles siguen apuntando acá y cuáles son fósiles. 29/29 clasificados: 12 apuntan acá, 4 ya se mudaron, 13 NO RESUELVEN. Lo que el DNS solo no puede decir es si el backend contesta, así que ésos quedan en `apunta-aca`, clase propia y NO `vivo`: decir «vivo» sin haber pedido una página sería la respuesta falsa con forma de respuesta que este censo existe para evitar. **Y un hueco de verdad: los `fosil-sin-dns` quedaban fuera de `entradas()`**, con el argumento de que un dominio sin DNS no necesita ningún paso. Cierto para el DNS, FALSO para lo que arrastra: son 13 de 29, y `terapeuta.ec` tenía 279 M de contenido y su bloque en el servidor web. Fuera de la lista eran invisibles, así que nadie decidía borrarlos y sus datos viajaban a la caja nueva por omisión — el «mudar fósiles» que este plan existe para evitar, y contra su propia regla 2 («lo que muere se dice por su nombre y con su tamaño, ANTES de borrar nada»). Ahora entran, con recomendación `muere` y el aviso de revisar qué dejaron en disco. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
21e7cc7159 | estado: cosecha granja 2026-09-11T21:01:56Z — avance del árbol KDE | ||
|
|
11ea3d66e4 | estado: cosecha granja 2026-09-11T20:50:23Z — avance del árbol KDE | ||
|
|
3aab82d6f6 |
planear: el centro de traducción ENGANCHADO al plan — y el plan que emitíamos no era TOML válido
El plan ya advertía «cambiar de servidor web obliga a REESCRIBIR la configuración entera». Cierto, y
la advertencia correcta, pero dejaba al humano con un párrafo y ninguna herramienta. Ahora es un PASO
con su comando literal: `traducir.py --from openrc --to arje --in /etc/init.d/caddy …`.
Dos traducciones distintas por servicio, y confundirlas es caro: la DECLARACIÓN (cómo se levanta,
`openrc`/`systemd` → tarjeta de arje) y la CONFIGURACIÓN (qué hace, sólo si se eligió un
equivalente). Un servicio que se muda a sí mismo no necesita la segunda: ofrecérsela es inventarle
trabajo. Los pares se le PREGUNTAN al registro de plugins, no se listan acá — una lista propia se
desincroniza del centro y el plan ofrecería una traducción que no existe. Sin par, el paso lo dice.
La verificación es HUMANA a propósito: `traducir.py` sale ≠0 cuando algo quedó sin traducir, así que
dar el paso por bueno por su código de salida sería al revés de lo que hay que mirar. Lo que verifica
es que alguien LEYÓ el acta.
**Y en el camino salió un fallo del producto: el plan no era TOML válido.** Apareció con el primer
comando multilínea, pero estaba latente desde el principio y tiene DOS modos:
· `awk "\$1==1"` —que está en el `verifica` de TODO servicio— con cadena básica da
`Unescaped '\' in a string`: el plan queda ILEGIBLE.
· `cmd --from x \` + salto: la cadena básica PARSEA y se come el salto y la sangría ⇒ el comando
que sale NO es el que se escribió, sin que nada falle. Ése es el peor.
El plan es el producto: se revisa, se versiona, se lleva a otro proveedor y se vuelve a correr. Uno
que no vuelve a parsear no sirve para ninguna de las dos cosas para las que existe. Arreglado con
cadena literal, y con un guardián que ESCRIBE Y RELEE antes de tocar el disco: si el TOML generado no
parsea, no se escribe nada. Convierte un fallo diferido —aparecía cuando alguien iba a EJECUTAR el
plan— en uno inmediato.
Probado de punta a punta: el comando que el plan emite, copiado tal cual, traduce el
`/etc/init.d/caddy` real de esta máquina a una tarjeta con `Restart{initial:3000}` (de su
`respawn_delay=3`) y reporta la única `reload()` que no se puede portar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
07cc386580 |
qdrant: murió a los 408 crates por el NOMBRE del wrapper del lab, no por qdrant
Primer intento en el worker: 1,5 h, 408 crates compilados, y esto:
"/src/vendor/protobuf-src/protobuf/configure" … "--host=/src/.hammer-zig"
Invalid configuration `/src/.hammer-zig': machine `/src/.hammer-unknown' not recognized
La cadena es qdrant → `raft-proto` (tikv/raft-rs) → `protobuf-build` → **`protobuf-src`**, que
compila protobuf 21.5 DESDE FUENTE con el crate `autotools` — o sea que ni siquiera usa el `protoc`
que acabo de meter en el catálogo. Y `autotools` adivina el triple `--host` **recortándole el sufijo
al nombre del compilador**. Leído en `vendor/autotools/src/lib.rs` del árbol de post-mortem, no
supuesto:
let host = cc_path.strip_suffix("-cc").or_else(|| cc_path.strip_suffix("-gcc"));
if let Some(host) = host { args.push(format!("--host={}", host)); }
El lab exporta `CC="$PWD/.hammer-zig-cc"` ⇒ recortar `-cc` deja `/src/.hammer-zig`, que viaja como
triple. **El fallo no tiene nada que ver con qdrant ni con protobuf: lo causa cómo se llama nuestro
wrapper.** Cualquier receta Cargo que arrastre el crate `autotools` va a chocar igual.
Con `compiler = "gcc"` el lab exporta `CC="gcc"`, al que no se le puede recortar `-cc` ni `-gcc` ⇒ el
crate **no añade `--host`** y configure corre nativo. El propio crate ya tiene un caso especial
`cc_path != "musl-gcc"`, señal de que la heurística es frágil y upstream lo sabe.
⚠ **Y NO se arregla renombrando el wrapper del lab, aunque sea lo obvio:** ese nombre vive dentro de
la cadena de la fase `compile`, que entra en `hash_inputs` ⇒ tocarlo re-hashea las **234 recetas Rust
del corpus**. Es una campaña con su propia verificación, no un arreglo de paso. La palanca por receta
es la correcta, y queda escrito en la receta para que el próximo que lo vea no reabra la discusión.
|
||
|
|
f52205af31 |
opensmtpd: la familia «correo» estaba vacía — y la colisión de símbolos se CONTÓ antes de arreglarla
Tercera de las cuatro familias que la tabla de `planear.py` daba SIN NADA en el catálogo. Quedan dos:
contenedores y base vectorial (qdrant está moliendo en el worker).
Cuál de los tres servidores es una decisión, y va con el motivo para poder discutirla: **postfix**
asume glibc en varios sitios (NIS, nsswitch, su `dict_nis`) y son ~250 kLOC — portarlo a musl es un
frente, no una receta; **exim** tiene una configuración que es literalmente un lenguaje de
programación, y esa superficie ha sido su fuente histórica de CVEs; **OpenSMTPD** viene de OpenBSD
con separación de privilegios POR DISEÑO, ~40 kLOC, ISC, y su rama «portable» existe justo para
construirse fuera de OpenBSD ⇒ musl es un objetivo previsto. Si alguien necesita las tablas
MySQL/LDAP de postfix esto no sirve — pero se discute con la razón delante en vez de descubrir la
ausencia durante una mudanza.
Verificado: los 10 binarios estáticos, CERO NEEDED, y `smtpd -n -f` sobre una `smtpd.conf` real
contesta `configuration OK`. REPRODUCE bit a bit.
**El muro fue una colisión de símbolos, y lo que importa es que se MIDIÓ antes de elegir el arreglo.**
OpenSMTPD-portable trae su propia **libtls** (la envoltura de LibreSSL) porque la necesita cuando se
construye contra OpenSSL; y OpenSSL 3 define en su capa de récords una función INTERNA con el mismo
nombre. Estático, las dos caen en el mismo binario:
ld.lld: error: duplicate symbol: tls_free
defined at ../../openbsd-compat/libtls/tls.c:708
defined at ssl/record/methods/tls_common.c:1473 in archive /usr/lib/libssl.a
En vez de suponer el tamaño del problema, se contó:
nm -g --defined-only openbsd-compat/libtls/*.o | sort -u → 158 símbolos
comm -12 <esos> <los globales de libssl.a> → **1**: tls_free
Uno solo ⇒ el arreglo es renombrar ése y nada más, con `--with-cppflags=-Dtls_free=…` (la perilla que
upstream ya expone). El `-D` alcanza toda la compilación de OpenSMTPD, así que renombra a la vez la
definición, la declaración de `tls.h` y los usos — consistente por construcción; la de OpenSSL vive
en un `.a` ya compilado y no se toca. Es seguro porque esa libtls es interna a este build.
Si hubieran sido docenas de símbolos, el arreglo correcto era otro —traer LibreSSL al corpus, que es
contra lo que upstream construye— y por eso se midió primero. Queda escrito en la receta para que el
día que OpenSSL sume otro choque se vuelva a contar en vez de apilar `-D`s.
Dos cosas más anotadas y no tapadas: `--with-libfts=/usr` es obligatorio porque `fts(3)` está en
glibc y NO en musl (el corpus tiene `musl-fts` justo para eso) y sin pasarlo `configure` lo buscaría
en el LAB, que no entra en `hash_inputs`; y los usuarios `_smtpd`/`_smtpq` todavía no existen en la
distro — se dejan en su nombre canónico a propósito, porque cambiarlos por `root` tiraría la
separación de privilegios, que es la razón principal para elegir este servidor.
|
||
|
|
294959c1da |
arranque y GC: las dos rutas EFI, guardián de ESP y el kernel vivo como raíz
Primera tanda de los ADR 0017/0018, con las mediciones que la corrigieron. ADR 0018 §3 — install-image-efi.sh y takana-live-install.sh escriben el kernel en \EFI\takana\takanax64.efi ADEMAS de la ruta fallback, con un guardián de capacidad que comprueba ANTES y con los números a la vista (duplicar el kernel cuesta, y el costo se dice). En el live-install la verificación es por TAMAÑO, no por presencia: un cp truncado en FAT32 deja el fichero ahí y test -e diría que todo salió bien. Verificado con imagen real en OVMF: las dos copias dan el mismo sha256 que el bzImage del store, y la imagen arranca hasta arje-zero PID1. CONTROL NEGATIVO: pisando la fallback con 4K de basura el firmware dice "No bootable option or device was found" y no aparece HAMMER-EFI ni una vez ⇒ el escenario de Windows, reproducido. Y lo que NO se pudo verificar cambia el alcance, así que va en el ADR: sin entrada NVRAM el firmware NO busca el vendor path. La copia vendor es hoy un seguro que no se cobra solo — el §3 es necesario y NO suficiente sin el §1. Eso asciende la receta efibootmgr a bloqueante. ADR 0017 §4 — store-gc.sh suma como raíz el kernel EN EJECUCIÓN, identificado por .config byte a byte. Sin esto el artefacto del kernel vivo cae en "superados" cuando la receta se movió, y se borra: el sistema sigue andando perfecto hasta el día que hace falta volver atrás. Probado en los tres sentidos, incluido el control que TIENE que seguir condenado. Además, dos bugs preexistentes que aparecieron al ir a medir: - install-image-efi.sh abortaba con "ROOTFS sin /sbin/init" en TODO rootfs sano: /sbin/init es un symlink ABSOLUTO (→/usr/bin/arje-zero) y [ -e ] lo sigue contra la raíz del HOST. Un chequeo que validaba algo distinto de lo que creía validar. Arreglado resolviendo el destino dentro del rootfs. - (no arreglado, es del entorno) el cp -al del staging da EXDEV si ROOTFS y STAGE no están en el MISMO MOUNT — y el bind-mount del store cuenta como otro mount aunque sea el mismo /dev/sdb. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj |