atuq: la bóveda ANDA hasta la etapa D — y con el navegador abierto quedaba muda por un tercer fallo
Con el arreglo de llimghi sellado, el guardián de metal pasó de la A a la D:
A ✓ sin la app dueña, el host contesta locked:true (y con ok:true, que es la trampa)
B ✓ con la app, vault.status dice ABIERTA — el socket sube en 1 s
C ✓ el diálogo ABRE en el compositor y se lo CONTESTA: «1» ⇒ yes, «2» ⇒ no (los dos sentidos)
D ✓ vault.save con consentimiento real guarda, y vault.match la encuentra SIN contraseña
E ✗ el navegador pide la contraseña y no llega nunca
La E destapó un fallo que no es de atuq ni de llimphi: el dueño de la bóveda atendía de a UN cliente
—`atender_cliente` no vuelve hasta que el cliente se va, y se lo llamaba en el hilo del accept—, así
que la primera conexión se quedaba con él mientras viviera. Y el caso normal es ése: Gecko lanza un
`puriy-costura` por PUERTO, ocho en atuq, y todos conectan al arrancar. La extensión de la bóveda
mandaba `vault.match` y no volvía nunca: sin insignia, sin log y sin error, indistinguible de «este
sitio no tiene contraseñas».
Control sin navegador, en los dos sentidos: un solo cliente contesta en milisegundos; con otro host
conectado y quieto, la misma pregunta queda colgada y la mata el timeout a los 30 s.
Arreglado en tawasuyu (`cf3540460`, un hilo por conexión; los diálogos los serializa ahora el Mutex
de la bóveda) con su test de regresión, que además cazó que el arreglo obvio —clonar el Dueno— borra
el socket en el Drop del primer hilo que termina. Los siete tests que ya había no podían ver el fallo:
abrían un cliente por vez, que es justo lo que producción nunca hace.
⚠ Y subir el pin volvió a chocar con el `Cargo.lock` abierto de tawasuyu. Lo que importa para la
próxima es CUÁL operación lo cierra: `cargo metadata` sin `--locked` da **1 línea** de diff y cero
checksums movidos; `cargo generate-lockfile` da 6.305 líneas y 750 checksums, e invalidaría el
vendoreo de todos. Publicado en `b80f7567c` con índice temporal (estaba MM).
Del lado del guardián, tres cosas que salieron de fallar:
· el contestador INSISTE hasta que la ventana se va — con el mismo `wtype rc=0`, una corrida
contestaba y otra volvía `denied`: la ventana entra al árbol del compositor antes de que llimphi
tenga el teclado enganchado, y un sleep más largo sólo mueve el borde;
· el contestador busca la ventana POR PATRÓN: con el navegador abierto, teclear «la enfocada» le
contestaría a la página, y eso es un sí que nadie dio;
· la sonda del chrome mira `isShownForTab` y la insignia ANTES de apretar — `triggerClickOrPopup`
sale por la puerta de atrás si la acción no está mostrada, y un click que no llega se ve igual que
una bóveda que no contesta. (Y `WebExtensionPolicy` no es global en el scope de una ventana: sale
del módulo. Eso costó una corrida.)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3150,6 +3150,39 @@ ficheros que otra sesión tiene en vuelo. Control: `git diff-tree -r --name-only
|
||||
push fue rechazado la primera vez porque el remoto se había movido: se rehízo el `commit-tree` sobre
|
||||
el `FETCH_HEAD` nuevo, comprobando antes que los dos ficheros no habían cambiado allá.
|
||||
|
||||
##### ⚠⚠ «SELLADA» en cero segundos: el latido REVIERTE la receta del worker (2026-09-18)
|
||||
|
||||
Al reconstruir con el pin nuevo, el log del worker dijo `### boveda SELLADA` casi al instante. No
|
||||
era. El worker había vuelto a la receta VIEJA y lo que «selló» fue un **acierto de caché sobre el
|
||||
artefacto anterior**:
|
||||
|
||||
```
|
||||
hub: recipes/boveda.toml → b3:8d1d7536 (pin eed3120b6)
|
||||
worker: recipes/boveda.toml → b3:59ffd74b (pin 23a292863, el de antes)
|
||||
```
|
||||
|
||||
**Quién la revierte:** el latido (`cosecha-cron.sh`) siembra `rsync -az --delete` hub→worker **al
|
||||
principio** de cada ciclo, y hace su `git pull --ff-only` **al final**. O sea que cada media hora el
|
||||
worker vuelve al árbol de ESE hub, que puede estar hasta un ciclo atrasado — y un `rsync` manual de
|
||||
una receta dura lo que tarde el siguiente latido. El cron, además, no corre en este hub: corre en el
|
||||
otro, con su propio checkout.
|
||||
|
||||
**Por qué es peligroso y no sólo molesto:** el modo de fallo no es un error, es un **éxito falso**.
|
||||
Si el artefacto de la receta vieja ya está sellado, el build imprime `SELLADA` en cero segundos y el
|
||||
operador lee exactamente lo que esperaba leer. Es la forma «un ausente falla ruidosamente; un vacío
|
||||
llega hasta el final diciendo que todo fue bien» de la regla 3, un piso más abajo.
|
||||
|
||||
**La mitigación, que es una línea y va DENTRO del mismo comando que toma el lock:** preguntarle al
|
||||
worker el hash y compararlo con el del hub antes de construir.
|
||||
|
||||
```sh
|
||||
h=$(./target/release/takana --store ./store hash recipes/boveda.toml | sed s/^b3://)
|
||||
[ "$h" = "$ESPERADO" ] || { echo "### RECETA REVERTIDA: el worker hashea ${h:0:12}"; exit 3; }
|
||||
flock -o work/.farm-build.lock ./target/release/takana --store ./store build recipes/boveda.toml
|
||||
```
|
||||
|
||||
Con eso el segundo intento dijo `### receta verificada 8d1d75369548` antes de compilar nada.
|
||||
|
||||
⚠ **El pin de `boveda` y `shuma-pregunta` ahora DIVERGE del de sus hermanas** (`eed3120b6` contra
|
||||
`23a292863`), y eso cuesta **un árbol de fuentes propio: otro vendoreo de 2,4 G**, porque el árbol se
|
||||
comparte por `<repo>-<sha>`. Es el precio de no mover las otras once recetas del monorepo en el mismo
|
||||
@@ -3160,6 +3193,77 @@ seis etapas declaradas y la A en verde: sin la app dueña, el host contesta `loc
|
||||
es exactamente lo que se puede afirmar hoy — que es mejor que un guardián que no existe y que uno que
|
||||
diera verde midiendo nada.
|
||||
|
||||
#### 7.octies Con el navegador abierto la bóveda queda MUDA — el dueño atendía de a un cliente (2026-09-18)
|
||||
|
||||
Con llimphi arreglado, el guardián de metal llegó hasta la etapa D —guardar una contraseña con
|
||||
consentimiento real y encontrarla de nuevo— y **se plantó en la E**, que es la que usa el navegador.
|
||||
El cuadro era éste, y ninguna de sus líneas es un error:
|
||||
|
||||
```
|
||||
BOVEDA CONECTADO puriy_costura ← la extensión conecta con su host
|
||||
BOVEDA-METAL EXTENSION cargada
|
||||
BOVEDA-METAL MOSTRADA true ← el botón de la extensión SÍ está para esa pestaña
|
||||
BOVEDA-METAL INSIGNIA "" ← …y la insignia está vacía
|
||||
BOVEDA-METAL DISPARO api ← se aprieta el botón
|
||||
(nada más. Ni diálogo, ni relleno, ni una línea de `fondo.js`)
|
||||
```
|
||||
|
||||
La extensión no dice nada porque **no tiene nada que decir**: `vault.match` se manda y **no vuelve
|
||||
nunca**. Sin respuesta no hay insignia, no hay log y no hay error — que desde el navegador es
|
||||
indistinguible de «este sitio no tiene contraseñas guardadas».
|
||||
|
||||
**La causa, medida y no deducida.** `pacha_boveda_daemon::Dueno::servir` llamaba a `atender_cliente`
|
||||
en el hilo del `accept`, y `atender_cliente` no vuelve hasta que el cliente se va ⇒ **la primera
|
||||
conexión se queda con el dueño mientras viva** y el resto espera en la cola del socket para siempre.
|
||||
Y el caso normal es justamente ése: **Gecko lanza un `puriy-costura` por PUERTO** —uno por extensión,
|
||||
ocho en `atuq`— y cada uno abre su conexión a la bóveda al arrancar (§7.quater ya había medido lo de
|
||||
«un host por puerto»; lo que faltaba era ver qué le hace eso al dueño).
|
||||
|
||||
El control, en los dos sentidos y sin navegador, dentro de la misma jaula:
|
||||
|
||||
```
|
||||
== un solo cliente → {"verb":"vault.status","locked":false} en milisegundos
|
||||
== con otro host conectado → colgado; lo mata el `timeout` a los 30 s
|
||||
```
|
||||
|
||||
**Arreglado allá** (`cf3540460`): un hilo por conexión. El argumento por el que se serializaba —dos
|
||||
diálogos de consentimiento a la vez es cómo alguien autoriza el que no era— sigue en pie, y ahora lo
|
||||
sostiene **el `Mutex` de la bóveda**, que quien atiende toma para responder y sostiene mientras
|
||||
pregunta. Lo que deja de serializarse es lo que nunca debió: estar conectado.
|
||||
|
||||
⚠ **Y el arreglo obvio estaba mal por una razón que no se ve leyendo:** clonar el `Dueno` para cada
|
||||
hilo hace que el primero que termina **borre el socket** (lo hace en su `Drop`, y está bien que lo
|
||||
haga: un socket huérfano deja al próximo cliente esperando en vez de decirle que no hay daemon). Por
|
||||
eso atiende un `Atendedor` —bóveda y a quién preguntarle, sin la ruta—. Lo cazó el test nuevo en el
|
||||
primer intento.
|
||||
|
||||
**El test, y por qué el crate no podía ver su propio fallo.** `dos_clientes_vivos_a_la_vez_son_
|
||||
atendidos`. Los siete que ya existían abrían un cliente, lo usaban y lo soltaban antes del siguiente
|
||||
—y el propio arnés del test llama a `atender_cliente` en secuencia—, así que **nunca hubo dos
|
||||
conexiones solapadas**: el test serializaba justo lo que producción no serializa nunca. El segundo
|
||||
cliente se pregunta en un hilo con `recv_timeout` a propósito, porque el fallo ES colgarse y un test
|
||||
que se cuelga no dice qué pasó. Probado en los dos sentidos: con el bucle viejo falla a los 5 s, con
|
||||
el nuevo pasa, y los otros siete siguen verdes.
|
||||
|
||||
##### ⚠ Y el lock de tawasuyu volvió a estar abierto — se cierra con UNA línea, no con mil
|
||||
|
||||
Subir el pin a `cf3540460` dio el error de siempre: `cargo vendor --locked` muere con «cannot update
|
||||
the lock file … because --locked was passed», **sin nombrar el crate**. Es el mismo modo de fallo que
|
||||
bloqueó a `atuq` tres semanas (§7.quinquies.bis), y vuelve cada vez que alguien agrega una dep de
|
||||
ruta sin actualizar el lock.
|
||||
|
||||
Lo que importa para la próxima vez es **cuál de las dos operaciones se usa para cerrarlo**:
|
||||
|
||||
| | diff | checksums movidos | sirve |
|
||||
|---|---|---|---|
|
||||
| `cargo metadata` sin `--locked` (mínima) | **1 línea** | **0** | ✅ |
|
||||
| `cargo generate-lockfile` | 6.305 líneas | 750 | ❌ invalida el vendoreo de todos |
|
||||
|
||||
La mínima agrega sólo la arista que falta. La otra re-resuelve el workspace entero y se ve igual de
|
||||
«correcta» hasta que uno mira el diff. Las dos en un árbol LIMPIO (`git archive | tar -x`), con el
|
||||
control de siempre: `cargo metadata --locked` falla antes y pasa después. Publicado en `b80f7567c`,
|
||||
con índice temporal porque el `Cargo.lock` estaba `MM` en el clon compartido.
|
||||
|
||||
## 8. Plan, por unidades de trabajo
|
||||
|
||||
Cada una cierra sola, se commitea y se pushea. El orden no es preferencia: cada una destraba a la
|
||||
|
||||
Reference in New Issue
Block a user