Pedido del usuario: «una lista de cada cosa con una corta descripción y la opción de yo elegir mudar,
mover a un directorio para respaldar ese directorio, o abandonarlo». Eran tres huecos distintos.
1. «CADA COSA». El censo medía RAÍCES: `/home` era UNA entrada de 25 G y UNA decisión, con repos,
SDKs, 4,3 G de caché y trabajo irrepetible adentro. Ahora emite un renglón por cosa —169 en
gioser contra 5—, un nivel hacia adentro de cada raíz y DOS en `/home`, porque su primer nivel
son usuarios y la decisión no es por usuario. `--solo-totales` conserva la vista vieja, que es la
que dimensiona la mudanza; las dos viajan en el censo (`datos` y `datos_raiz`).
⚠ El primer intento mandaba las ~110 sondas en UNA llamada, se pasaba del timeout y devolvía
lista VACÍA: «no hay datos» en vez de «no pude medirlos». Va en tandas, y una tanda que falla se
nombra.
2. «UNA CORTA DESCRIPCIÓN», y son hechos: lo sirve el servidor web · lo usa un proceso vivo · es
repo git y a dónde apunta · tiene cambios sin commitear · adentro hay node_modules/target/venv ·
lo más nuevo que hay dentro. «Sin señales» también es un hecho, y es el que dice dónde mirar.
3. «ELEGIR ENTRE TRES»: DECISIONES pasa de (muda, muere) a (muda, RESPALDA, muere). Faltaba el
camino que se usa de verdad —no lo quiero corriendo allá, tampoco lo quiero perder—; con dos
opciones, todo lo dudoso se marcaba `muda` y la mudanza engordaba.
La regla de fondo no cambió (qué datos VALEN no lo dice la máquina), pero cuatro hechos sí los
sabe: servido/usado ⇒ muda · caché ⇒ muere · repo limpio con remoto ⇒ muere · repo sin remoto o
sucio ⇒ respalda. Y el cruce que evita el peor error: un remoto que apunta a ESTA MISMA máquina
no es un respaldo, es el mismo disco (medido: /mnt/vvv/humanoid → ssh://git@127.0.0.1:2345).
4. LAS DECISIONES SE GUARDAN. `--decide` las usaba sólo para el plan de esa corrida: cerrabas la
terminal y se perdían las 169. Ahora reescribe el censo (temporal+fsync+rename, `.bak`, y releído
antes de pisar nada; aborta si perdió alguna). Para eso hubo que hacer el emisor IDEMPOTENTE:
escribía `decision = ""` fijo, así que re-emitir un censo decidido duplicaba la clave y el TOML
dejaba de parsear — la herramienta no podía reescribir su propio fichero.
5. EL PASO `respaldo`: corral por defecto leído de respaldo-storagebox.sh (no una segunda dirección
a mano), nombre aplanado, y NO se instala en el destino. Sin corral, aborta: un rsync a ninguna
parte devuelve 0 y se lee como un respaldo. Probarlo con rotura a propósito Y con el control que
tiene que pasar destapó dos bugs: `--mkpath` es obligatorio (el primer respaldo siempre estrena
un nivel) y un FICHERO no lleva barra final — `rsync fichero/ dst/` es un error, y el paso de
`datos` tenía el mismo defecto latente. La verificación es rsync en seco contra el destino real,
no `du` ni `find`: el Storage Box no tiene find, no acepta tuberías, y su `du` comprime.
6. De paso: `aplicar.py --dry-run` decía «✅ todos los pasos ejecutados y verificados» sin haber
ejecutado nada. Ahora dice EN SECO: N mostrados, NINGUNO ejecutado y NINGUNO verificado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Con las decisiones puestas, se movió lo que no dependía de nada más. Cada copia se verificó del lado
del destino, que es lo que el rsync con exit 0 y 1367 artefactos vacíos dejó escrito:
27 guiones (75 KB) → takana:/work/rescate-guiones sha256sum -c ⇒ 27 OK
memoria de Claude (6,3 M) → takana:/root/.claude 733 ficheros = 733
transcripts (1,3 G) → StorageBox claude-gioser rsync -an ⇒ 1 pendiente (el vivo)
clave PRIVADA de release → takana:/root/.config/takana/keys sha256 igual + pública == trust/
credenciales y config → takana:/work/mudanza-secretos 27 ficheros = 27
estado de sigma/tupu/willay → takana:/work/mudanza-estado 351 ficheros, 68 M
Las credenciales NO se instalaron en su sitio a propósito: la caja ya tiene su /root/.ssh, su
crontab y su Caddyfile, y pisarlos rompe lo que funciona. El .gitconfig es el caso más claro — trae
los `insteadOf` que reescriben remotos. Se instalan cuando se mude el hub. La excepción es la clave
de release, que sí fue a su ruta canónica: sin ella nadie puede volver a firmar el repo, y eso no
falla el día que se borra gioser sino la próxima vez que alguien publica.
Y un «576 M contra 1,3 G» que parecía copia truncada: el Storage Box COMPRIME, y su shell
restringida no tiene find ni acepta tuberías, así que el conteo de ficheros ahí no se puede hacer.
Lo que sí: preguntarle a rsync en seco, que compara contra el destino real. Un número que no cuadra
merece una segunda medición antes de un diagnóstico.
No se movieron ~10 G de árboles personales de /home ni /opt: qué datos valen no se deduce de la
máquina, y ésos son proyectos del usuario, no servicios de un censo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El pin de `puriy-costura` subio a `23a292863` y con eso se apagaron los dos rojos del cable. La suite
entera sobre `b3:e556024b`: **19 verdes, 0 rojos, 32,1 min**. No se toco ningun guardian — que la
suite existiera ANTES del arreglo es lo que permite afirmarlo: el cuadro de ayer y el de hoy son el
mismo instrumento.
La medicion que cierra es la que abrio el §7.quinquies, leida al reves: `strings` sobre el binario
sellado da `vault` **10** veces donde daba 0, con `cas`/`sct` de control; y `vigia-atuq-verbos` pasa
de 3 verbos sin dueno a cero, con su control positivo intacto.
Lo destrabo publicar el `Cargo.lock` de tawasuyu (`23a292863` alla), regenerado en un arbol limpio:
215 lineas, todas de contabilidad de deps por ruta, cero `checksum` y cero `source` movidos. Y el
remate, que es la leccion: **ese lock era byte a byte el que la otra sesion ya tenia en su arbol sin
commitear**. Tres semanas de bloqueo y el fichero correcto estaba escrito, sin empujar, a un
directorio de distancia. Antes de decir «bloqueado por otro repo», mirar si lo que falta no esta ya
hecho y sin publicar.
Commiteado alla con indice temporal (`GIT_INDEX_FILE` + `commit-tree`), que es la unica forma de
publicar una ruta en `MM` sin llevarse por delante lo del otro agente: su arbol quedo byte a byte
igual, comprobado con `cmp` antes y despues.
⚠ Y la cabecera del runner se desmintio SOLA dos veces: anuncio «un rojo» cuando eran dos, y «dos»
cuando ya no quedaba ninguno. Un numero escrito como prediccion envejece callado. Con todo en verde
el numero ya no distingue un producto sano de un runner que no corre nada, asi que lo que sostiene la
corrida queda escrito: la puerta del sello (verificada contra un store vacio) y el control interno de
cada guardian.
El usuario pidió el detalle de los binarios que ningún paquete provee, que es lo que la mudanza no
puede reconstruir. Medido en los tres directorios (`/usr/local/bin`, `~/.local/bin`,
`/root/.local/bin`):
tawasuyu 57 ficheros 1003 M salida del monorepo ⇒ se reconstruye
terceros 10 ficheros 841 M kiro-cli 571 M, kcl, qdrant, listmonk… ⇒ se re-bajan
respaldo/duplicado 21 ficheros 876 M /root/.local/bin es una COPIA de kiro-cli + el CLI claude
guiones 27 ficheros <1 M git-deploy, webhook-deploy.py, mirada-session… ⇒ NADA los provee
Lo caro no es lo valioso: 1,8 G de los 2,7 son reconstruibles o duplicados, y lo único que no está
en ningún repo ni paquete son 27 guiones que suman menos de un megabyte. Y los 572 M que el censo no
veía (§6.34) eran justamente la copia duplicada de kiro-cli.
De `.claude` decide mi criterio, por pedido: de 6,8 G viajan 15 M — `projects/*/memory` (6,2 M),
`settings.json`, `plugins` e `history.jsonl`. `jobs` (5,3 G), `downloads`, `archivo-sesiones` y los
caches no viajan; los transcripts crudos (973 M) van al Storage Box, no a la caja: no hacen falta
para funcionar, la memoria es su destilado.
⚠ Y la trampa que haría inútil todo eso: la memoria se indexa POR RUTA (`-mnt-vvv-takana`), y en la
caja el repo está en `/opt/takana` — copiarla tal cual deja 163 ficheros que NO se encuentran, sin
que nada falle.
⚠ Segundo hallazgo: la caja no tiene usuario `sergio` (sólo root, sshd, gitea, proxy) y shuma corre
sin privilegio en gioser. La tarjeta que lo declare tiene que crear la cuenta, y el payload Native
de arje no tiene campo de usuario.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El censo ya tenía tres estados a propósito (`ausente` / `sin_permiso` / `ok`), pero el tercero sólo
se detectaba sobre la PROPIA ruta: cuando el que no deja pasar es un ANCESTRO, `[ -e "$p" ]`
contesta que no existe y la ruta caía en `ausente`, que el censo descarta sin decir nada.
Medido en gioser: `/root/.local` es 0700 ⇒ un censo corrido como `sergio` daba `/root/.local/bin`
por AUSENTE. Como root son 572 M de binarios puestos a mano que ningún paquete provee — justo la
clase que la mudanza no puede reconstruir.
Arreglado: si un ancestro existe y no es atravesable, el estado es `sin_permiso`. Probado en los dos
sentidos, que es lo que separa un guardián de un adorno:
ancestro 0700 de otro dueño → sin_permiso (sale en el ⚠ del resumen)
ruta que NO existe de verdad → ausente (no se reporta) ← el control que tiene que pasar
Con el arreglo, el censo sin privilegio pasa de 2 rutas ciegas a 13, entre ellas el home entero de
`artix`, que antes no figuraba de ninguna manera.
Y el §6.34 con el estado MEDIDO de la mudanza hoy: de los 29 dominios queda UNO sirviéndose desde
gioser (`sergio.gioser.net`); los otros seis vivos ya contestan 200 contra 2.29.29.217. El rescate
del §6.20 sigue coincidiendo bit a bit con cuatro de los seis procesos que corren desde un inodo
borrado, y los otros dos tienen en disco una versión más nueva. Lo que queda abierto son los pasos 6
y 7: el montón de daemons propios (shuma, willay, pacha, tejido, tupu, matilda…) y los datos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La primera version dejaba afuera al guardian de notificaciones (`dunst-headless.sh --via-atuq`) con
la excusa de que vive en el arnes de sway, y lo escribia en la cabecera «para que su ausencia no se
lea como cobertura». Eso dura tres dias: al cuarto la lista ES la cobertura y el parrafo no lo lee
nadie. Corrido aparte: verde en 180 s. Entra.
Es ademas el que mide lo que ningun auditor de ELF puede ver: una pagina llama `new
Notification(...)`, `libxul` hace `dlopen("libnotify.so.4")` —que no es NEEDED de nada—, el bus
activa dunst y dunst DIBUJA. Cinco piezas y la unica forma de saber que estan las cinco es verlo.
Para que entrara, la lista pasa a llevar el COMANDO de cada guardian en vez de su nombre de fichero:
lo que lo tenia afuera era el despacho por extension, no una decision.
Corrida entera de los diecinueve sobre `b3:e556024b`: **17 verdes, 2 rojos, 32,2 min**, que es
exactamente lo que la cabecera predice — y la cabecera es el control, no un adorno.
Los caros: sct 242 s, archivo semantico 238 s, ia 182 s, instalacion 180 s, notificaciones 180 s.
Los cuatro primeros salen en 5 s y no tocan el navegador.
Del `access.log`, sin ambigüedad. La IP del usuario (`45.234.61.160`, autenticada como `sergio`,
2709 peticiones) tuvo tres silencios: **93,2 min (11:59:37 → 13:32:49 UTC)**, 50,1 min (10:44:58 →
11:35:06) y 10,0 min (11:45:06 → 11:55:05). Los tres son el `timeout` de 10 minutos del ban
disparándose una y otra vez: cada vez que volvía, su primera ráfaga lo baneaba de nuevo. El corte
grande terminó exactamente cuando vacié los sets.
🧨 Y LA EXENCIÓN QUE TENÍA NO SERVÍA: la ACL histórica de squid exime `45.234.60.0/24` y él entra
desde `45.234.61.160` — **una red de al lado**. Parecía cubierto y no lo estaba. La pertenencia se
MIDE (`ip in red`), no se deduce del parecido del prefijo.
DECISIÓN DE FONDO: en un servicio que YA AUTORIZA no va límite por tasa. El uso de esta caja es
INTENSIVO —varias sesiones de agente en paralelo, cada una abriendo túneles a la vez— y un límite
calibrado para «una persona normal navegando» no protege de nada: el atacante ajusta su ritmo, el
usuario no puede trabajar más despacio. Squid ya pide usuario y contraseña y tiene su ACL. El ban del
puerto queda apagado (60000/min, ráfaga 1000) y contra un flood queda el tope GLOBAL
`syn_nuevas_por_seg`, que no distingue usuarios ni banea a nadie por ser rápido.
Control tras aplicar: su túnel pasando —`45.234.61.160 … CONNECT api.anthropic.com:443 sergio`— y
`45.234.61.160 ∈ 45.234.60.0/23` comprobado con aritmética de redes, no a ojo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Preguntaron si el proxy estaba caído. NO lo estaba: el ente llevaba horas corriendo con ↻ 0 y
contestando 407. Lo caído era el ACCESO — el cortafuegos de ayer baneaba a los autorizados.
La prueba en una línea: **154.197.1.2, una IP que está en la ACL de squid (gente con contraseña),
apareció en `@ban4`**, con el contador del ban en 38.246 paquetes tirados.
LA CAUSA NO ES LA TASA, ES LA RÁFAGA: `limit rate over 300/minute burst 5 packets` tolera cinco
conexiones por encima del promedio, y un navegador o un agente detrás de un proxy abre DECENAS de
CONNECT en el mismo instante aunque su promedio sea bajísimo. Y como @ban4 es UN SOLO set consultado
antes que todo, caer por el puerto del proxy te tira también el web y el git.
⚠ El tipo ya traía el campo `rafaga` (default 5) con un comentario que dice «para un servicio web va
alto (100-200)». Otra vez leí el .ron de EJEMPLO y no el TIPO.
Hecho, en orden de urgencia: flush de los sets (servicio restaurado en el acto) · los 21 IPs y 5
redes de la ACL de squid a `confiables` · `rafaga` por servicio según su clientela (ssh 5, git 20,
web 100, proxy 200) · proxy a 1200/min con ban de 60 s, que es red de contención contra un flood y
no control de uso. Control después: CERO autorizados en el set, 24 baneados (escáneres) y tráfico
real pasando —`154.194.14.37 … CONNECT api.deepseek.com:443 sigma`, 298 K tunelados—.
⚠⚠ Y un fallo de método que casi lo tapa: **`nft -f` SUMA a la tabla existente**. Cargué el reglaset
corregido encima del viejo y quedaron 30 reglas con las VIEJAS ADELANTE, así que los `confiables`
nuevos no se evaluaban nunca. Por eso `cortafuegos apply-input` borra la tabla antes de cargar.
La lección: un cortafuegos correcto y uno usable no son lo mismo, y la diferencia NO se ve al
aplicarlo. El control que lo habría cazado es el que no corrí: una ráfaga de conexiones desde una IP
que no esté en `confiables`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La prueba de la caja recién instalada —`cargo build` en un proyecto nuevo, sin flags ni rutas a
mano— falló con `linker `cc` not found`, y destapó que la pieza que había empaquetado era falsa:
**cargo NO LEE `/etc/cargo/config.toml`**. Sólo lee `$CARGO_HOME/config.toml`, los
`.cargo/config.toml` del proyecto y sus padres, y lo que se le pase con `--config` — que es lo que yo
venía pasando a mano en cada prueba, y por eso «funcionaba».
Y el arreglo no era mover el fichero: **el que enlaza es `rustc`, y rustc no lee la config de
cargo**. Las flags van DENTRO del compilador, en el spec del triple:
· `linker = "ld.lld"` + `linker_flavor = Gnu(Cc::No, Lld::Yes)` — sin esto rustc busca `cc`, que una
caja takana no tiene.
· `crt_static_default = true` — sin esto enlaza dinámico y pide `-lgcc`, que no existe (la distro se
construye con zig, que trae compiler-rt).
· `crt_static_allows_dylibs = true` — su pareja obligatoria: sin ella `crt-static` apaga los dylibs
y con ellos los PROC-MACROS (serde_derive, clap_derive, las 44 recetas `cargo-*` con `derive`).
El compilador en sí sigue dinámico: el `bootstrap.toml` fija `crt-static = false` para el build, y
eso gana sobre el default del spec.
⇒ `cargo-config` (receta, árbol y copia en scripts/) se van enteros: un paquete que instala un
fichero que nadie abre es peor que no tenerlo.
Y de paso, `lld21` instala los cuatro alias como SYMLINKS y no como copias: cmake los duplicaba, 5 ×
96 M = 478 M de artefacto para 97 M de enlazador. Ahora 108 M, y `ld.lld` sigue despachando por
`argv[0]`, que es como upstream lo diseñó.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sale `rust-toolchain-bin` —799 M de bytes AJENOS sellados tal cual (`foreign`)—, que era el escalón 1
del SDD 31 y cumplió su función: con él se construyó el escalón 2, y el escalón 2 ya compila,
enlaza y corre.
Entran TRES, que son el mismo hecho («esta caja compila Rust») partido en piezas:
· `rust` (353 M) — el compilador y cargo, de la granja, desde fuente.
· `lld21` — el enlazador. NO es opcional: una caja takana no tiene `cc`, y sin enlazador `rustc`
compila objetos y no produce un ejecutable.
· `cargo-config` — receta nueva que publica `/etc/cargo/config.toml` con las tres flags que unen a
los dos. Sin ellas `cargo build` muere con «linker `cc` not found»: el toolchain estaría completo
y MUDO, que es [[subcomando-sin-driver]] otra vez. Es config de SISTEMA y no de usuario a
propósito — un `~/.cargo/config.toml` funcionaría para quien lo escribió y no para el siguiente.
⚠ Y `rust` se re-hashea otra vez (702094a8 → fe277b32) por una línea que descubrió el control de
PATH: **x.py instala en `/usr/local` por defecto, y el PATH de una caja takana es
`/bin:/usr/bin:/sbin:/usr/sbin`** — medido en la caja. O sea que el perfil habría proyectado un
`rustc` perfecto que nadie puede invocar: «existe ≠ se encuentra». Ahora `[install] prefix = "/usr"`,
como el resto del corpus.
Control previo que sí pasó: `rustc` y `cargo` corren SIN `LD_LIBRARY_PATH` — su `RUNPATH` es
`$ORIGIN/../lib`, así que la proyección del artefacto basta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Un cambio de dos líneas en `llvm21` re-selló tres artefactos y los molió la granja sola, en orden,
por dependencia: llvm21 95956a16→bd4a4094, lld21 27be53c1→95a4794b, rust 015a07fb→702094a8.
llc --version Stack dump: + segfault → LLVM 21.1.2, Optimized build
opt --version ídem → LLVM 21.1.2
ld.lld «not built with zlib» → ENLAZA
rustc --version → 1.97.0 (built from a source tarball)
--print target-list → x86_64-alpine-linux-musl
cargo build + correr → 0,16 s y el binario habla
La config del sitio pasa a usar `lld` (más rápido); el `ld` de binutils queda documentado como
alternativa que también funciona.
LA REGLA QUE SE GANÓ SU LUGAR: **`link = "static"` en el encabezado decide cosas que la receta no
dice**. Van tres, todas medidas: apagó los threads de openssl, le mintió a libtool, y acá rompió los
registros estáticos de LLVM (`cl::opt`, `TargetRegistry`) porque el `static-pie` descarta sus
constructores globales. Con C++ grande la postura por defecto pasa a ser `dynamic` y medirlo — y el
control no es que selle: es CORRER UNA HERRAMIENTA del artefacto.
Y el corolario de por qué nadie lo vio en meses: el único consumidor de llvm21 era rust, que usa
`llvm-config` y las `.a`, nunca una herramienta. **Un artefacto puede estar roto en todo lo que nadie
usa** — como los 44 `cargo-*` sin cargo y los 23 binarios sin cargador.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
$ cargo build --offline
Compiling prueba v0.1.0 … Finished in 0.44s
$ ./target/debug/prueba
cargo, rustc y ld: los tres nuestros
`cargo` y `rustc` son los que construyó la granja; el enlazador es el `ld` de nuestro `binutils`.
EL MURO 8 SE CERRÓ SIN `lld`. La cadena, que es lo que vale: `-C linker-flavor=ld.lld` → «linker
`lld` not found» (el build con LLVM externo no genera rust-lld); `rust.lld = true` → el bootstrap lo
RECHAZA; `lld21` propio → crasheaba con cualquier entrada; `lld21` con zlib → **no se puede encender
desde ahí**, porque `LLVMConfig.cmake` dice `LLVM_ENABLE_ZLIB 0` y el build standalone lo hereda.
Lo que faltaba NO era un enlazador: era decirle a rustc que enlace en ESTÁTICO. Con `+crt-static`,
rustc usa su propio libunwind/compiler_builtins y deja de pedir `-lgcc` — que es lo que rompía el
intento con `ld`, porque la distro se construye con zig (compiler-rt) y libgcc no existe. Y estático
es el modo natural de musl.
⇒ `scripts/servidor/cargo-config.toml` (instalado en `/etc/cargo/config.toml`) cierra el pendiente
«config del sitio» del §5, con las tres flags y el porqué de cada una.
🧨 Y `lld21` destapó algo que nadie había visto: **las 79 herramientas de `llvm21` están ROTAS**.
`llc --version` crashea igual —también en el worker— porque salen `static-pie` y el enlace estático
descarta los constructores globales de los que dependen `cl::opt` y `TargetRegistry`. Funciona lo
que no los necesita (`llvm-ar`, `llvm-config`) y revienta lo que sí; nadie lo notó porque `rust` usa
`llvm-config` y las `.a`, nunca una herramienta. Queda como deuda DECLARADA: arreglarlo (o encender
zlib) re-hashea `llvm21` y con él `rust`. Hoy no bloquea nada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
353 M, construido por la granja desde `llvm21` del corpus y el stage0 del lab. Los controles, en la
caja (que sí tiene cargador musl; el worker no):
rustc --version → 1.97.0 … (built from a source tarball) ← NUESTRO
rustc --print target-list → x86_64-alpine-linux-musl ← EL PARCHE, DENTRO
cargo --version → 1.97.0 … (built from a source tarball)
Y compila: `--emit=obj` da un objeto de 3,5 K y `--crate-type=rlib` una biblioteca, sin enlazador.
🧱 MURO 8, YA MEDIDO: el compilador no trae con qué ENLAZAR. En una caja sin `cc` —cualquier takana
que no sea un hub— `-C linker-flavor=ld.lld` da «linker `lld` not found» y el `ld` de binutils muere
con «cannot find -lgcc». La causa estaba en una línea del log que parece informativa:
skipping llvm-tools (x86_64-alpine-linux-musl): external LLVM
Al usar LLVM externo —que es lo que queremos— x.py se salta las herramientas de LLVM, y `rust-lld`
es una de ellas. El prebuilt oficial sí la trae, y por eso el escalón 1 enlazaba. Y `llvm21` no
sirve de repuesto: NO publica ningún `lld` (medido sobre sus 1,6 G).
Dos salidas: (a) `[rust] lld = true`, que construye rust-lld desde el llvm-project vendoreado y deja
el toolchain autosuficiente; (b) publicar `lld` desde llvm21 y fijar el linker en un
`/etc/cargo/config.toml` del sitio.
Y la lección de método: el artefacto SELLÓ, reproduce y NO ENLAZA. Es [[subcomando-sin-driver]] en
el corazón del toolchain. Un `build ok` no es la prueba: la prueba es el objeto en el disco.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>