basquetWi + New ticket
movilba MOVILBA-261

El post-receive de dev01 serializa el build y gatea por salud

Done normal unassigned

El 2026-09-05 05:54 ART dev01 se cayo 11 minutos: 500 en TODAS las rutas, /api/health incluida. Dos pushes a 60 s de distancia (v5.4.209 de coder02, v5.4.210 de coder03) corrieron el build a la vez sobre /srv/dev-movilba-web. Ningun defecto de codigo en ninguno de los dos commits. Resuelto por coder03 con un redeploy bump-only, v5.4.211 / 0c68f5d5, tomando el lock a mano; /api/health 200 a las 06:05. EL LOCK NO VA EN gitdeploydev.sh (mi propuesta original, corregida). Ese script no construye nada: su propio comentario en :424 lo dice y el build corre en el post-receive del bare, SINCRONICAMENTE, durante el `git push`. Un flock en el script serializaria solo a los que llaman al script; un `git push dev HEAD:dev` pelado desde cualquier worktree dispara el mismo build sin lock. Un guard en el que llama no puede proteger un recurso al que llegan todos los demas. El punto de estrangulamiento es /srv/dev-movilba-web.git/hooks/post-receive, que ademas vive en el host cuyo directorio se corrompe. EL MECANISMO, leido del hook :83-102 (DERIVADO DE LA FUENTE, NUNCA REPRODUCIDO): la corrida B corre `rm -rf node_modules .turbo .pnpm-store` mientras A construye; el `rm -rf "$NEXT_OLD"` de B destruye la COPIA DE ROLLBACK de A; el build de A falla y su restore no encuentra nada que restaurar; B falla igual. Neto: no queda ningun .next, el servicio contesta 500 y el exit 1 del hook no va a ninguna parte. El safe swap es correcto para UNA corrida y es exactamente lo que convierte un build fallido en una CAIDA cuando dos se superponen, porque el rollback es un camino fijo unico y sin dueño. PARCHE LISTO Y APROBADO, en /resource/movilba/deploy/2026-09-05-movilba-261-post-receive-lock-y-health.patch, md5 4dd24b2f8566064019f706e4dd731bca. Tres cambios: flock sobre un fd dedicado con espera acotada de 900 s que nombra al holder al vencer y nunca usa LOCK_NB; rollback POR CORRIDA .next.old.$$ con barrido de huerfanos como movilba_srv; y /api/health tras el restart con el puerto leido de la unit systemd. DUEÑO: fila 201 del registro, primario nw-lezama-cc. bin-lezama-cc lo reviso contra el hook vivo y lo aplica apenas Elazar nombre el destino literal. audit-mba-l-cc sin hallazgos abiertos. BLOQUEADO EN ELAZAR: la regla global "never modify, create or delete code in /srv/" impide aplicarlo, y "movilba owns its deploy scripts" no nombra el destino literal. La mitad de sh.git quedo VACIA: no hay diff que hacer en gitdeploydev.sh, mas alla del gate de salud del lado del cliente que bin-lezama-cc ya shipeo en v2.14 (exit 4), que es lo unico que puede FALLAR un deploy - el gate de adentro del hook es una alarma, porque post-receive corre despues de actualizar la ref y no puede rechazar el push.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 5w ago
  • wi-cli-venus spec · 5w ago
    DUEÑO: fila 108 del registro de maintainers, primario bin-lezama-cc, backup bin-venus-cc. bin-lezama-cc tiene el aceptar/rechazar. Dato que bin-venus-cc aporto sin que se lo pidieran: la fila estaba MAL hasta hace tres horas, nombraba a bin-lezama-ca, un asiento Codex vivo pero al que ningun nudge llega. Preguntar por el dueño ayer habria ruteado el trabajo a nadie. PREMISA CONFIRMADA POR UN SEGUNDO CARRIL, no tomada de mi reporte: bin-venus-cc grepeo su propio checkout de venus, mismas 529 lineas o sea la misma version, y flock/lockfile/LOCK/api-health/curl-health dan dos hits, los dos prosa en comentarios (:161, :425). Sin mutex y sin chequeo de salud post-deploy. DISEÑO ACEPTADO POR bin-venus-cc: flock por correctitud, lock del hub por visibilidad, ESPERAR en contencion. El motivo por el que flock es el primitivo correcto es mas angosto que "es un mutex de verdad": lo que se corrompe es un DIRECTORIO LOCAL en UN host, y un lock tiene que vivir en el mismo alcance que la cosa que protege. El lock del hub es advisory, tiene TTL y se puede olvidar, asi que solo puede ser una convencion. CUATRO TRAMPAS, de bin-venus-cc, que shipeo esta misma guarda en cinco emisores de venus y la erro dos veces: A. UN CAMINO COMPARTIDO NO ES UN LOCK COMPARTIDO. Los dos que llaman tienen que derivar el MISMO archivo de lock desde la misma clave por la misma transformacion. Dos derivaciones que parecen equivalentes dan dos archivos, cero exclusion mutua, y las dos revisan como correctas. Se verifica por INTEROP - derivar el camino desde cada call site y comparar las cadenas literales - nunca leyendo el codigo. B. NUNCA LOCK_NB ACA. El segundo deploy casi siempre se sigue queriendo. Un lock no bloqueante abandona justo el caso contendido para el que existe el lock. Acotar la espera y loguear un skip al vencimiento, en vez de bloquear para siempre: un holder trabado tiene que anunciarse, o se presenta como un deploy que en silencio dejo de ocurrir. C. MUESTREAR EL ESTADO ADENTRO DEL LOCK. Todo lo que se consulte para decidir si seguir - salud, estado de git, un arbol sucio - se lee DESPUES de adquirir, nunca antes. Una lectura previa al lock es TOCTOU por construccion. D. EL CHEQUEO DE SALUD ES UNA SEGUNDA FUNCION, y es la que habria bajado esta caida de once minutos a segundos. Lo que realmente paso: los dos builds fallaron, .next desaparecio, y el servicio siguio contestando 500 sin que nada lo reportara. Un deploy que falla y reporta exito es peor que uno que falla ruidosamente. Gatear la salida por /api/health en 200 y fallar la corrida cuando no. VERIFICACION, la parte que mas se saltea: disparar dos deploys en el mismo instante y afirmar que el segundo ESPERA, sobre un arbol descartable y NO sobre dev01. Y la negativa tambien: matar al holder a mitad de build y confirmar que el que espera adquiere en vez de colgarse para siempre. Un lock que solo se tomo sin contencion esta sin probar. EL ITEM SE QUEDA EN ESTE TABLERO (la caida y la evidencia son de este carril) pero el ARREGLO aterriza en sh.git bajo el asiento de bin-lezama-cc. Este carril no edita el archivo.
  • wi-cli-venus note · 5w ago
    CORRECCION DE MI PROPIA PROPUESTA: el lock NO va en gitdeploydev.sh. Ese script no construye nada. Su propio comentario en :424 lo dice y yo lo lei por arriba: el post-receive del bare corre `pnpm install` y `pnpm build` SINCRONICAMENTE durante el `git push`. Lo unico que hace el script en la colision es la etapa dev-push, `git push "$DEV_REMOTE_NAME" HEAD:dev` en :435. Un flock ahi serializa solo a los que llaman al script; un `git push dev HEAD:dev` pelado desde cualquier worktree dispara el mismo build sin ningun lock. Lockear al que llama no puede proteger un recurso al que llegan todos los demas. EL PUNTO DE ESTRANGULAMIENTO ES /srv/dev-movilba-web.git/hooks/post-receive. Ademas es donde lo pone la regla de alcance del propio bin-venus-cc: ese archivo vive en el host cuyo directorio se corrompe. EL MECANISMO REAL, leido del hook :83-102, dos corridas A y B (DERIVADO DEL ARCHIVO, NO REPRODUCIDO): - :83 B corre `rm -rf node_modules .turbo .pnpm-store` MIENTRAS A ESTA CONSTRUYENDO. Eso solo alcanza para el ENOENT de pages-manifest.json que encontro coder03. - :95-97 A hace `rm -rf .next.old` y despues `mv .next .next.old`; su copia de rollback queda como el unico build que sobrevive. - B llega a :96 y su `rm -rf .next.old` DESTRUYE LA COPIA DE ROLLBACK DE A. Su propio `mv .next .next.old` de :97 no hace nada, tapado por `|| true`, porque A ya se llevo .next. - El build de A falla y va al restore de :100-101; `mv .next.old .next` falla en silencio detras de `|| true` porque la copia ya no existe. B falla y restaura de la misma copia ausente. - Neto: no queda ningun .next, el servicio en marcha contesta 500 y el exit 1 del hook no va a ninguna parte. El safe swap es real y correcto para UNA corrida. Convierte un build fallido en una CAIDA en cuanto dos se superponen, porque el rollback es un camino fijo unico y sin dueño. CONSECUENCIA DE OWNERSHIP: 261 son DOS items con DOS dueños. El mutex y el gate de salud van en el hook, que NO es sh.git y NO es de este carril - rige la regla permanente de no modificar nada en /srv/, y no se toco. gitdeploydev.sh (bin-lezama-cc, fila 108) queda sin diff: el diff que se le iba a aplicar no habria evitado esto. Falta identificar al dueño del hook en el registro y abrir la mitad correspondiente. LO QUE SOBREVIVE DEL DISEÑO ACORDADO, ahora bien ubicado: flock arriba de todo en el hook, asi serializa todo push venga de donde venga; espera acotada y despues un skip logueado, nunca LOCK_NB; todo insumo de seguir/abortar muestreado DESPUES de adquirir; /api/health gateando la salida y fallando la corrida si no da 200. Dos agregados que fuerza el mecanismo de arriba: el camino de rollback tiene que ser POR CORRIDA y no el `.next.old` fijo compartido, y el `rm -rf node_modules` de :83 tiene que quedar adentro del lock o corrompe un build que ya esta corriendo. SIGUE VALIENDO, de bin-lezama-cc: el camino literal del lock derivado en cada call site y comparado COMO CADENAS, y la prueba de contencion sobre un arbol descartable con su negativa - matar al holder a mitad de build y ver que el que espera adquiere en vez de colgarse. NO EJERCITADO: nadie corrio todavia dos hooks a la vez para ver desaparecer .next.old. El interleaving de arriba es una LECTURA del archivo, no una observacion.
  • wi-cli-venus spec · 5w ago
    PARCHE FINAL Y APROBADO: /resource/movilba/deploy/2026-09-05-movilba-261-post-receive-lock-y-health.patch, md5 4dd24b2f8566064019f706e4dd731bca. Supersede 91a36807, aaaacd87 y bfd2c67e. Destino /srv/dev-movilba-web.git/hooks/post-receive. bash -n limpio. audit-mba-l-cc sin hallazgos abiertos; bin-lezama-cc (fila 201 del registro, primario nw-lezama-cc) lo reviso contra el hook vivo y lo aplica apenas Elazar nombre el destino literal. TRES CAMBIOS: (1) flock sobre un fd dedicado, espera acotada de 900 s, nombra al holder al vencer, nunca LOCK_NB; (2) rollback POR CORRIDA .next.old.$$ mas barrido de huerfanos como movilba_srv; (3) /api/health tras el restart, con el puerto LEIDO de la unit systemd y 3042 solo como fallback. DOS CORRECCIONES QUE ME HICIERON Y QUE VALEN MAS QUE EL PARCHE: - bin-lezama-cc: post-receive corre DESPUES de actualizar la ref y NO puede rechazar el push. El exit 1 llega al cliente como salida remota y el push igual tiene exito. O sea que este gate es una ALARMA, no un gate: hace que la caida se anuncie sola en vez de aparecer once minutos despues. El que realmente falla un deploy es el gate del lado del que llama, gitdeploydev.sh v2.14 exit 4. Los dos se quedan: uno agarra todo push incluido un `git push dev HEAD:dev` pelado, el otro frena al carril que corrio el script. - audit-mba-l-cc y bin-lezama-cc, por separado y por el mismo camino: mi control del barrido plantaba el directorio como rob y barria como rob, y el huerfano de produccion es de movilba_srv. El 1 a 0 es una medicion verdadera del artefacto equivocado. No pude construir el fixture que decide - `sudo -n -u movilba_srv` pide password en este carril - asi que el sudo entro por el modelo de permisos y porque todo rm vecino ya lo necesita, NO por un control que paso. Queda registrado como INDECIDIDO, no como aprobado. CHEQUEO POST-ATERRIZAJE, y como leerlo (nota mecanica de audit): el glob lo expande el shell que LLAMA, como el usuario del hook, antes de que corra sudo. Si no hay coincidencias le pasa el literal `.../.next.old.*` a rm, que no borra nada y sale callado por el `|| true`. "El barrido no logueo nada" es la salida esperada TANTO en el caso sano COMO en el roto. Por eso el chequeo es CONTAR los directorios antes y despues; el silencio no es exito. SIN EJERCITAR, lista completa: el hook real nunca corrio con este parche; la rama de FALLA del gate de salud nunca disparo contra un build genuinamente roto (vale hacer un push que no compile a proposito una vez que aterrice); el barrido esta verificado solo entre mismo usuario; y los 900 s solo se prueban en el primer push contendido de verdad. BLOQUEADO EN ELAZAR: la regla global "never modify, create or delete code in /srv/" impide aplicarlo. "movilba owns its deploy scripts" no nombra el destino literal y un GO relayado que no lo nombra no lo cubre. Dato aportado por bin-lezama-cc: la prohibicion es de hecho solo de lezama, 1 de 3 hosts, y nunca estuvo en el global-detail.md compartido. APARTE, FUERA DE ALCANCE Y NO MIO: ese hook guarda una password de sudo como literal en la linea 9. No se cita, no se mueve y no se rota - regla permanente. Elevado a Elazar por CLI.
  • wi-cli-venus titleChanged · 5w ago
    El post-receive de dev01 serializa el build y gatea por salud
  • wi-cli-venus descriptionChanged · 5w ago
    was: El 2026-09-05 05:54 ART dev01 se cayo 11 minutos. Dos deploys a 60 s de distancia (v5.4.209 de coder02, v5.4.210 de coder03) corrieron `next build` a la vez en /srv/dev-movilba-web y cada uno borro el .next del otro a mitad de build: ENOENT de pages-manifest.json y _buildManifest.js.tmp en pnpm-build.log, los dos builds fallaron, .next quedo sin existir y el servicio en marcha contesto 500 en TODAS las rutas, /api/health incluida. Ningun defecto de codigo en ninguno de los dos commits. Resuelto por coder03 con un redeploy bump-only, v5.4.211 / 0c68f5d5, tomando el lock a mano; /api/health 200 a las 06:05. La causa es que gitdeploydev.sh NO TIENE MUTEX. Hoy la unica proteccion es que cada carril se acuerde de pedir el lock del hub `dev01-deploy` antes y soltarlo despues del 200 - una convencion que se sostiene sola hasta que dos carriles shipean al mismo minuto, que es exactamente lo que paso. Lo que hay que hacer: que el script tome `dev01-deploy` el solo (POST /lock, holder = nombre del carril, TTL corto acorde a lo que tarda un build) y lo suelte al terminar, y que ante un 409 lock_held ESPERE o falle nombrando al que lo tiene, en vez de arrancar el build igual. CUIDADO ANTES DE TOCARLO: gitdeploydev.sh vive en ~/.local/bin, que es el checkout de sh.git, no en este repo. Hay que leer sh-git.md antes de editar y confirmar el maintainer en el registro de maintainer-agents ANTES de asignar el trabajo - no es un archivo de movilba-web y este carril no lo ownea. La verificacion tiene que incluir la rama que hoy no se ejercita: dos deploys simultaneos a proposito, y ver que el segundo espera en vez de romper el .next del primero.
  • wi-cli-venus verified · 5w ago
    El barrido YA NO ESTA INDECIDIDO. bin-lezama-cc lo decidio desde la politica de sudoers, no desde un fixture: `sudo -n -l -U rob` da (movilba_srv) NOPASSWD para /usr/local/bin/pnpm y /usr/bin/rm. O sea que rm SI esta concedido sin password como movilba_srv, secure_path resuelve el rm pelado a /usr/bin/rm, y el destino es de movilba_srv asi que puede recursar por propiedad. El barrido borra de verdad. Sacado de la lista de razonado-no-medido. POR QUE NO LO PUDE MEDIR YO: la concesion es POR COMANDO y por host. Yo probe con `sudo -n -u movilba_srv mkdir/touch/chmod`, que no estan en la lista, y me pidio password - eso se lee como 'no tengo sudo' cuando en realidad es 'no tengo sudo PARA ESE COMANDO'. Probar una politica de sudoers con un comando arbitrario da la respuesta equivocada. Misma familia que la sonda con otro driver: el arnes difiere del camino real justo en la propiedad que se mide. ADEMAS EXPLICA UNA ASIMETRIA HEREDADA QUE NINGUNO TENIA MOTIVO PARA ENTENDER: `mv` NO esta en la concesion, y por eso el hook pipea SUDO_PASSWORD en sus dos llamadas a mv y usa -n en todo el resto. No es inconsistencia de estilo, la traza el archivo de sudoers. Importa antes de que alguien 'ordene' un -S convirtiendolo en -n y se coma una falla silenciosa detras de un || true. SIN EJERCITAR, ahora TRES y no cuatro: el hook real nunca corrio con este parche; la rama de falla del gate de salud nunca disparo contra un build roto; los 900 s no se prueban hasta el primer push contendido.
  • wi-cli-venus blocked · 5w ago
    Live hook (06:52) breaks every dev deploy: sudo -n -u movilba_srv pnpm install -> 'sudo: a password is required' x8 in logs/pnpm-install.log; deploy.json 'pnpm install failed'. BUILD_ID still 06:43 while package.json is 5.4.222. v5.4.220/221/222 unbuilt. Reported to coder02 + bin-lezama-cc.
  • wi-cli-venus unblocked · 5w ago
  • wi-cli-venus completed · 5w ago
    Live post-receive already carries mutex+health-gate, landed by nw-lezama-cc, superior to the approved patch (chgrp, fd-200 inheritance note, run wrapper, ZOLD_DIR, SERVICE_ACTIVE_NOW re-test, stderr alongside log_json). Approved patch dry-run failed 3/4 hunks against the live file; forcing it would have duplicated the mutex block 102 lines out of position, past the serialization boundary. Do not reapply.
task
5w ago by wi-cli-venus
5w ago
2026-09-05 12:23