[Release prod] Enrique Martínez Lozano + carta «Hacia el corazón» — ejecución y findings #182

Open
opened 2026-07-25 11:03:47 +00:00 by rafa · 11 comments
Owner

Handoff de producción — Enrique Martínez Lozano + carta «Hacia el corazón»

Relacionado con #174 (cierre autónomo de carta) y #181 (derivados/TTS).
Este issue es el registro operativo único para el release y para los findings del operador.

Estado local aprobado

  • Comentario ES local #55041: El tesoro está ya en nosotros.
    • Categorías: Comentarios al evangelio + Feadulta.
    • Audio local: wordpress/wp-content/uploads/tts/55041.mp3 (2,221,101 bytes, voz NicoFeadulta2026).
  • Traducciones del comentario aprobadas: EN #55047, FR #55048, IT #55049, PT #55050.
  • Carta ES candidata local #55042: Hacia el corazón.
    • Traducciones: EN #55051, FR #55052, IT #55053, PT #55054.
    • QA editorial completado; Enrique aparece una única vez en Evangelio y comentarios al Evangelio, después de Fray Marcos y antes de Pagola. No aparece en Artículos seleccionados.

El render local se verificó, incluido reproductor TTS y la carta FR.

Producción: origen y guardarraíl crítico

  • Carta ES real en producción: #54914, título Hacia el corazón, estado publish.
  • La candidata local tiene fea_phase_a_source_prod_id=54914.
  • Por tanto, se debe actualizar la carta real #54914, nunca clonar otra carta ES.

Prohibido

python3 scripts/sync_translations_to_prod.py --ids 55041,55042

Su dry-run demostró que clonaría #55042 como una segunda carta ES; no actualiza #54914.

Preflight obligatorio (sin escritura)

cd /home/rafa/joomla-migration
python3 scripts/sync_translations_to_prod.py --ids 55041 --dry-run
python3 scripts/sync_audio_to_prod.py --ids 55041 --dry-run

Estos dry-runs ya pasaron en local:

  • artículo: plan de grupo Polylang ES + EN/FR/IT/PT;
  • audio: #55041 planificado sin errores.

Antes de escribir en producción:

  1. backup y snapshot server-side de #54914 (título, contenido, autor, fecha, categorías, estado y hash);
  2. confirmar que el hash actual de la carta coincida con el snapshot esperado o registrar cualquier divergencia;
  3. si hay divergencia editorial, parar y responder en este issue antes de continuar.

Ejecución solicitada al operador de producción

  1. Crear en producción el grupo nuevo de Enrique (ES + EN/FR/IT/PT) desde los artefactos locales, inicialmente como draft. Registrar los cinco IDs reales.
  2. Subir el MP3 local de Enrique y asociarlo al ID ES real de producción creado en el paso anterior (no asumir que será 55041). Verificar fichero, URL y metas fea_audio_done, fea_audio_url, fea_audio_voice.
  3. Actualizar exclusivamente título/contenido de la carta real #54914 con la candidata local #55042; conservar autor, fecha, categorías y estado existentes salvo incompatibilidad documentada.
  4. Crear o actualizar EN/FR/IT/PT de la carta contra el origen real #54914 y enlazarlas correctamente en Polylang. No crear una segunda carta ES.
  5. Validar server-side antes de publicar:
    • Enrique está una sola vez en Evangelio y comentarios al Evangelio, entre Fray Marcos y Pagola.
    • Enrique no aparece en Artículos seleccionados.
    • Cada carta traducida enlaza a Enrique en su propio idioma cuando exista el destino correspondiente.
    • Enlaces externos ES sin post traducido disponible se conservan como fallback; no inventar destinos.
    • El post ES muestra el reproductor/audio correcto.
  6. Sólo con todas las verificaciones verdes, promover los nuevos drafts a publish.

Verificación y rollback

  • Validar siempre por servidor (wp eval, wp post get, consultas Polylang/metas); no depender de navegador headless/Cloudflare.
  • Si falla una precondición o una comprobación, no publicar el lote: mantener drafts, restaurar el backup o revertir la carta #54914 al snapshot previo.

Respuesta obligatoria del operador en ESTE issue

Responder con un comentario usando exactamente esta estructura:

## Resultado de release

**Estado:** `completado` | `bloqueado` | `rollback aplicado`
**Ejecutado por:** <operador y fecha/hora>

### Preflight
- Backup/snapshot: <ruta o identificador>
- Carta #54914 antes: <hash, estado>
- Dry-runs: <PASS/FAIL + salida resumida>

### IDs y URLs en producción
- Enrique ES: #<id> — <URL>
- Enrique EN/FR/IT/PT: #<id> ...
- Carta ES: #54914 — <URL>
- Carta EN/FR/IT/PT: #<id> ...
- Audio: <ruta, tamaño, URL, voice meta>

### Verificaciones server-side
- [ ] Categorías y autor correctos
- [ ] Polylang completo
- [ ] Posición única de Enrique correcta
- [ ] Ausencia en Artículos seleccionados
- [ ] Enlaces por idioma / fallbacks justificados
- [ ] Audio y metas correctos

### Findings / desviaciones
- <ninguno, o descripción con evidencia y decisión>

### Rollback
- <no requerido, o acción y resultado>

Referencia local

docs/releases/enrique-2026-07-25-prod-dry-run.md

No subir ni aplicar cambios fuera de este alcance.

## Handoff de producción — Enrique Martínez Lozano + carta «Hacia el corazón» Relacionado con #174 (cierre autónomo de carta) y #181 (derivados/TTS). **Este issue es el registro operativo único para el release y para los findings del operador.** ## Estado local aprobado - Comentario ES local `#55041`: **El tesoro está ya en nosotros**. - Categorías: `Comentarios al evangelio` + `Feadulta`. - Audio local: `wordpress/wp-content/uploads/tts/55041.mp3` (2,221,101 bytes, voz `NicoFeadulta2026`). - Traducciones del comentario aprobadas: EN `#55047`, FR `#55048`, IT `#55049`, PT `#55050`. - Carta ES candidata local `#55042`: **Hacia el corazón**. - Traducciones: EN `#55051`, FR `#55052`, IT `#55053`, PT `#55054`. - QA editorial completado; Enrique aparece una única vez en *Evangelio y comentarios al Evangelio*, después de Fray Marcos y antes de Pagola. No aparece en *Artículos seleccionados*. El render local se verificó, incluido reproductor TTS y la carta FR. ## Producción: origen y guardarraíl crítico - Carta ES real en producción: **`#54914`**, título `Hacia el corazón`, estado `publish`. - La candidata local tiene `fea_phase_a_source_prod_id=54914`. - Por tanto, se debe **actualizar la carta real `#54914`**, nunca clonar otra carta ES. ### Prohibido ```bash python3 scripts/sync_translations_to_prod.py --ids 55041,55042 ``` Su dry-run demostró que clonaría `#55042` como una segunda carta ES; no actualiza `#54914`. ## Preflight obligatorio (sin escritura) ```bash cd /home/rafa/joomla-migration python3 scripts/sync_translations_to_prod.py --ids 55041 --dry-run python3 scripts/sync_audio_to_prod.py --ids 55041 --dry-run ``` Estos dry-runs ya pasaron en local: - artículo: plan de grupo Polylang ES + EN/FR/IT/PT; - audio: `#55041` planificado sin errores. Antes de escribir en producción: 1. backup y snapshot server-side de `#54914` (título, contenido, autor, fecha, categorías, estado y hash); 2. confirmar que el hash actual de la carta coincida con el snapshot esperado o registrar cualquier divergencia; 3. si hay divergencia editorial, parar y responder en este issue antes de continuar. ## Ejecución solicitada al operador de producción 1. Crear en producción el grupo nuevo de Enrique (ES + EN/FR/IT/PT) desde los artefactos locales, inicialmente como `draft`. Registrar los cinco IDs reales. 2. Subir el MP3 local de Enrique y asociarlo al **ID ES real de producción** creado en el paso anterior (no asumir que será `55041`). Verificar fichero, URL y metas `fea_audio_done`, `fea_audio_url`, `fea_audio_voice`. 3. Actualizar exclusivamente título/contenido de la carta real **`#54914`** con la candidata local `#55042`; conservar autor, fecha, categorías y estado existentes salvo incompatibilidad documentada. 4. Crear o actualizar EN/FR/IT/PT de la carta contra el origen real `#54914` y enlazarlas correctamente en Polylang. No crear una segunda carta ES. 5. Validar server-side antes de publicar: - Enrique está una sola vez en *Evangelio y comentarios al Evangelio*, entre Fray Marcos y Pagola. - Enrique no aparece en *Artículos seleccionados*. - Cada carta traducida enlaza a Enrique en su propio idioma cuando exista el destino correspondiente. - Enlaces externos ES sin post traducido disponible se conservan como fallback; no inventar destinos. - El post ES muestra el reproductor/audio correcto. 6. Sólo con todas las verificaciones verdes, promover los nuevos drafts a `publish`. ## Verificación y rollback - Validar siempre por servidor (`wp eval`, `wp post get`, consultas Polylang/metas); no depender de navegador headless/Cloudflare. - Si falla una precondición o una comprobación, no publicar el lote: mantener drafts, restaurar el backup o revertir la carta `#54914` al snapshot previo. ## Respuesta obligatoria del operador en ESTE issue Responder con un comentario usando exactamente esta estructura: ```markdown ## Resultado de release **Estado:** `completado` | `bloqueado` | `rollback aplicado` **Ejecutado por:** <operador y fecha/hora> ### Preflight - Backup/snapshot: <ruta o identificador> - Carta #54914 antes: <hash, estado> - Dry-runs: <PASS/FAIL + salida resumida> ### IDs y URLs en producción - Enrique ES: #<id> — <URL> - Enrique EN/FR/IT/PT: #<id> ... - Carta ES: #54914 — <URL> - Carta EN/FR/IT/PT: #<id> ... - Audio: <ruta, tamaño, URL, voice meta> ### Verificaciones server-side - [ ] Categorías y autor correctos - [ ] Polylang completo - [ ] Posición única de Enrique correcta - [ ] Ausencia en Artículos seleccionados - [ ] Enlaces por idioma / fallbacks justificados - [ ] Audio y metas correctos ### Findings / desviaciones - <ninguno, o descripción con evidencia y decisión> ### Rollback - <no requerido, o acción y resultado> ``` ## Referencia local `docs/releases/enrique-2026-07-25-prod-dry-run.md` No subir ni aplicar cambios fuera de este alcance.
Author
Owner

Resultado de release

Estado: bloqueadorollback aplicado
Ejecutado por: operador de producción, 25-jul-2026. Resultado registrado por Hermes porque el operador no pudo autenticarse para comentar en Gitea.

Preflight

  • Backup/snapshot de carta ES #54914: logs/release-issue-182/backups/prod-54914-before-20260725T111148Z.json.
  • Snapshot del cluster Polylang previo: logs/release-issue-182/backups/prod-54914-polylang-cluster-before-20260725T111337Z.json.
  • Carta #54914 antes: SHA-256 5e7eb1b880bfc6311edb78ac8b6e81639f5b4502282a1b182c610b1d01308b69.
  • Dry-run de traducciones: PASS, grupo de cinco posts draft.
  • Dry-run de audio: PASS, ok=1 skip=0 error=0.

IDs y URLs en producción

  • Enrique ES: #54997draft.
  • Enrique EN/FR/IT/PT: #54998/#54999/#55000/#55001draft.
  • Audio ES asociado al ID real #54997:
    • /web/wp-content/uploads/tts/54997.mp3
    • 2,221,101 bytes
    • https://www.feadulta.com/wp-content/uploads/tts/54997.mp3
    • voz NicoFeadulta2026.
  • Carta ES restaurada: #54914publish.
  • Cluster de carta restaurado: EN #54979, FR #54980, IT #54981, PT #54982publish.

Aplicación y rollback

  • Se actualizó inicialmente la carta real #54914 y sus traducciones existentes, sin crear una segunda carta ES.
  • Los enlaces de Enrique en las cartas EN/FR/IT/PT se remapearon de IDs locales 55047–55050 a IDs reales 54998–55001; evidencia: logs/release-issue-182/card-links-remap-20260725T111700Z.json.
  • Durante la verificación, read_full devolvió HTML de WordPress Database Error para varios posts. No se promovieron los drafts.
  • Se restauró el cluster completo de carta desde snapshot; evidencia: logs/release-issue-182/rollback-carta-cluster-20260725T112000Z.json.

Verificación posterior de Hermes (sólo lectura, server-side)

  • Issue #182 tenía 0 comentarios antes de este registro; la credencial usada ahora por Hermes sí es válida para el Gitea vivo.
  • Lecturas mínimas de #54981 (post, idioma Polylang, grupo y categorías): correctas, sin error SQL reportado.
  • Se hicieron 3 rondas de read_full para #54997, #54914, #54979–#54982: 18/18 JSON válidos, 0 fallos. El error original parece transitorio/intermitente, no una corrupción demostrada de #54981.

Findings / desviaciones

  • Bloqueante pendiente: explicar y estabilizar el origen del HTML Database Error que apareció durante la verificación original. Que ahora las lecturas pasen no autoriza reintentar el release sin revisar los logs de MySQL/PHP/WordPress del intervalo 20260725T111700Z–112000Z.
  • No hay publicación nueva de Enrique; los cinco posts siguen en draft. El rollback de la carta queda confirmado.

Rollback

  • Aplicado correctamente al cluster de carta #54914/#54979/#54980/#54981/#54982.
  • No se requiere más rollback por ahora.

Siguiente paso propuesto

Diagnosticar en sólo lectura los logs de base de datos/PHP/WordPress del intervalo del incidente, documentar causa y prueba de estabilidad, y abrir un nuevo intento únicamente desde los drafts existentes #54997–#55001 (sin clonar ni recrear la carta).

## Resultado de release **Estado:** `bloqueado` — `rollback aplicado` **Ejecutado por:** operador de producción, 25-jul-2026. Resultado registrado por Hermes porque el operador no pudo autenticarse para comentar en Gitea. ### Preflight - Backup/snapshot de carta ES `#54914`: `logs/release-issue-182/backups/prod-54914-before-20260725T111148Z.json`. - Snapshot del cluster Polylang previo: `logs/release-issue-182/backups/prod-54914-polylang-cluster-before-20260725T111337Z.json`. - Carta `#54914` antes: SHA-256 `5e7eb1b880bfc6311edb78ac8b6e81639f5b4502282a1b182c610b1d01308b69`. - Dry-run de traducciones: **PASS**, grupo de cinco posts draft. - Dry-run de audio: **PASS**, `ok=1 skip=0 error=0`. ### IDs y URLs en producción - Enrique ES: `#54997` — **draft**. - Enrique EN/FR/IT/PT: `#54998/#54999/#55000/#55001` — **draft**. - Audio ES asociado al ID real `#54997`: - `/web/wp-content/uploads/tts/54997.mp3` - 2,221,101 bytes - `https://www.feadulta.com/wp-content/uploads/tts/54997.mp3` - voz `NicoFeadulta2026`. - Carta ES restaurada: `#54914` — **publish**. - Cluster de carta restaurado: EN `#54979`, FR `#54980`, IT `#54981`, PT `#54982` — **publish**. ### Aplicación y rollback - Se actualizó inicialmente la carta real `#54914` y sus traducciones existentes, sin crear una segunda carta ES. - Los enlaces de Enrique en las cartas EN/FR/IT/PT se remapearon de IDs locales `55047–55050` a IDs reales `54998–55001`; evidencia: `logs/release-issue-182/card-links-remap-20260725T111700Z.json`. - Durante la verificación, `read_full` devolvió HTML de WordPress **Database Error** para varios posts. No se promovieron los drafts. - Se restauró el cluster completo de carta desde snapshot; evidencia: `logs/release-issue-182/rollback-carta-cluster-20260725T112000Z.json`. ### Verificación posterior de Hermes (sólo lectura, server-side) - Issue #182 tenía 0 comentarios antes de este registro; la credencial usada ahora por Hermes sí es válida para el Gitea vivo. - Lecturas mínimas de `#54981` (post, idioma Polylang, grupo y categorías): correctas, sin error SQL reportado. - Se hicieron 3 rondas de `read_full` para `#54997`, `#54914`, `#54979–#54982`: **18/18 JSON válidos**, 0 fallos. El error original parece transitorio/intermitente, no una corrupción demostrada de `#54981`. ### Findings / desviaciones - **Bloqueante pendiente:** explicar y estabilizar el origen del HTML `Database Error` que apareció durante la verificación original. Que ahora las lecturas pasen no autoriza reintentar el release sin revisar los logs de MySQL/PHP/WordPress del intervalo `20260725T111700Z–112000Z`. - No hay publicación nueva de Enrique; los cinco posts siguen en draft. El rollback de la carta queda confirmado. ### Rollback - **Aplicado correctamente** al cluster de carta `#54914/#54979/#54980/#54981/#54982`. - No se requiere más rollback por ahora. ### Siguiente paso propuesto Diagnosticar en sólo lectura los logs de base de datos/PHP/WordPress del intervalo del incidente, documentar causa y prueba de estabilidad, y abrir un nuevo intento únicamente desde los drafts existentes `#54997–#55001` (sin clonar ni recrear la carta).
Author
Owner

Seguimiento de diagnóstico — límite de conexiones MySQL + correcciones obligatorias del verificador

Hallazgo confirmado: límite por usuario de MySQL

Lectura server-side confirmada de la base MySQL de producción:

  • usuario WordPress: myfeadulta@localhost;
  • max_connections=500 global;
  • pico global histórico: Max_used_connections=61;
  • grant efectivo del usuario WordPress: WITH MAX_USER_CONNECTIONS 3;
  • conexiones activas observadas durante el diagnóstico: 3–4; Threads_running=1.

Por tanto, el global no estaba saturado, pero tres procesos PHP/WP simultáneos del mismo usuario pueden provocar un error de conexión. Esto es una causa probable fuerte del HTML Database Error, aunque los logs accesibles ya no conservan el texto literal del incidente.

El script estándar sync_translations_to_prod.py ejecuta sus clones en serie; no hay evidencia en los artefactos de que él mismo lanzase cinco clones paralelos. El riesgo está en cualquier orquestador que paralelice helpers/verificaciones o solape procesos PHP.

Hallazgo adicional: falsos negativos en server-verification-20260725T111550Z.json

El rollback fue correcto por principio de seguridad, pero la verificación tiene defectos que deben repararse antes de un nuevo intento:

  1. Busca enlaces propios como /?p=<id>, mientras el contenido de producción usa permalinks. El nombre de Enrique se encontraba exactamente una vez en cada carta, pero el check devolvió path_count=0/has_own_link=false por esa expectativa errónea.
  2. Compara la carta contra contenido local sin aplicar la misma normalización/remapeo de URLs realizada al desplegar; por eso carta_content_matches_local=false no es una comparación válida.
  3. Prueba el reproductor mediante render público de un draft; esa ruta no es un test válido para un post que todavía no es público. Debe verificar metas/archivo y render server-side con contexto de post.

Gates obligatorios antes de reintentar

  1. DBA/hosting: elevar de forma controlada el límite del grant de myfeadulta@localhost (propuesta inicial: 15) y verificar con SHOW GRANTS; no modificar max_connections global. La operación debe hacerla quien administra MySQL y registrar el valor final.
  2. Runner: una sola ejecución a la vez (flock), worker/concurrencia 1, sin xargs -P, procesos en background, Promise.all ni verificaciones paralelas.
  3. Verificador: corregir los tres falsos negativos anteriores y probarlo localmente contra contenido con permalinks y drafts.
  4. Preflight nuevo: comprobar dos lecturas JSON consecutivas de los 10 posts afectados, snapshot fresco del cluster publicado y acceso de comentario a este issue antes de la primera escritura.
  5. Reutilizar, no recrear: mantener/revalidar los drafts existentes #54997–#55001; no clonar otro grupo ni tocar la carta hasta que los gates 1–4 estén verdes.
  6. Aplicación y promoción: actualizar el cluster de carta en serie, validar con permalinks/IDs reales, y publicar Enrique sólo cuando todos los checks corregidos estén verdes. Ante una respuesta HTML/no JSON, detener y restaurar el cluster desde el snapshot nuevo.

No se han hecho cambios en producción durante este diagnóstico.

## Seguimiento de diagnóstico — límite de conexiones MySQL + correcciones obligatorias del verificador ### Hallazgo confirmado: límite por usuario de MySQL Lectura server-side confirmada de la base **MySQL de producción**: - usuario WordPress: `myfeadulta@localhost`; - `max_connections=500` global; - pico global histórico: `Max_used_connections=61`; - grant efectivo del usuario WordPress: **`WITH MAX_USER_CONNECTIONS 3`**; - conexiones activas observadas durante el diagnóstico: 3–4; `Threads_running=1`. Por tanto, el global no estaba saturado, pero tres procesos PHP/WP simultáneos del mismo usuario pueden provocar un error de conexión. Esto es una **causa probable fuerte** del HTML `Database Error`, aunque los logs accesibles ya no conservan el texto literal del incidente. El script estándar `sync_translations_to_prod.py` ejecuta sus clones en serie; no hay evidencia en los artefactos de que él mismo lanzase cinco clones paralelos. El riesgo está en cualquier orquestador que paralelice helpers/verificaciones o solape procesos PHP. ### Hallazgo adicional: falsos negativos en `server-verification-20260725T111550Z.json` El rollback fue correcto por principio de seguridad, pero la verificación tiene defectos que deben repararse antes de un nuevo intento: 1. Busca enlaces propios como `/?p=<id>`, mientras el contenido de producción usa permalinks. El nombre de Enrique se encontraba exactamente una vez en cada carta, pero el check devolvió `path_count=0`/`has_own_link=false` por esa expectativa errónea. 2. Compara la carta contra contenido local sin aplicar la misma normalización/remapeo de URLs realizada al desplegar; por eso `carta_content_matches_local=false` no es una comparación válida. 3. Prueba el reproductor mediante render público de un `draft`; esa ruta no es un test válido para un post que todavía no es público. Debe verificar metas/archivo y render server-side con contexto de post. ### Gates obligatorios antes de reintentar 1. **DBA/hosting:** elevar de forma controlada el límite del grant de `myfeadulta@localhost` (propuesta inicial: 15) y verificar con `SHOW GRANTS`; no modificar `max_connections` global. La operación debe hacerla quien administra MySQL y registrar el valor final. 2. **Runner:** una sola ejecución a la vez (`flock`), worker/concurrencia `1`, sin `xargs -P`, procesos en background, `Promise.all` ni verificaciones paralelas. 3. **Verificador:** corregir los tres falsos negativos anteriores y probarlo localmente contra contenido con permalinks y drafts. 4. **Preflight nuevo:** comprobar dos lecturas JSON consecutivas de los 10 posts afectados, snapshot fresco del cluster publicado y acceso de comentario a este issue antes de la primera escritura. 5. **Reutilizar, no recrear:** mantener/revalidar los drafts existentes `#54997–#55001`; no clonar otro grupo ni tocar la carta hasta que los gates 1–4 estén verdes. 6. **Aplicación y promoción:** actualizar el cluster de carta en serie, validar con permalinks/IDs reales, y publicar Enrique sólo cuando todos los checks corregidos estén verdes. Ante una respuesta HTML/no JSON, detener y restaurar el cluster desde el snapshot nuevo. No se han hecho cambios en producción durante este diagnóstico.
Author
Owner

Corrección de seguridad — no cambiar la configuración MySQL

El límite MAX_USER_CONNECTIONS 3 es una restricción operativa preexistente con la que Fe Adulta ya ha publicado correctamente. No está autorizado cambiar el grant, max_user_connections ni ninguna configuración MySQL como parte de este release.

El Gate 1 propuesto en el comentario anterior queda anulado. El reintento debe resolver el problema en la capa de ejecución: una sola operación PHP/WP remota a la vez, sin verificaciones/helpers concurrentes, y con una investigación del runner concreto que produjo el fallo.

No se ha aplicado ningún cambio de MySQL ni de producción durante el diagnóstico. Se documentará aquí el flujo serial mínimo y la causa encontrada antes de reintentar.

## Corrección de seguridad — no cambiar la configuración MySQL El límite `MAX_USER_CONNECTIONS 3` es una restricción operativa preexistente con la que Fe Adulta ya ha publicado correctamente. **No está autorizado cambiar el grant, `max_user_connections` ni ninguna configuración MySQL como parte de este release.** El Gate 1 propuesto en el comentario anterior queda **anulado**. El reintento debe resolver el problema en la capa de ejecución: una sola operación PHP/WP remota a la vez, sin verificaciones/helpers concurrentes, y con una investigación del runner concreto que produjo el fallo. No se ha aplicado ningún cambio de MySQL ni de producción durante el diagnóstico. Se documentará aquí el flujo serial mínimo y la causa encontrada antes de reintentar.
Author
Owner

Propuesta de reintento de bajo riesgo — sin cambios MySQL

Investigación completada:

  • No hay código concurrente (ThreadPool, asyncio, xargs -P, parallel, etc.) en los runners versionados.
  • El sincronizador estándar recorre posts en serie, pero cada helper abre un SSH + proceso PHP nuevo.
  • El runner exacto usado para #182 no fue guardado junto con los artefactos; por tanto no hay evidencia suficiente para afirmar que ejecutó cinco escrituras en paralelo.
  • El apply registrado duró 32 segundos e hizo cinco drafts, audio y cinco actualizaciones de carta; eso exige prudencia, pero no demuestra concurrencia.

Decisión operativa

No cambiar MySQL. MAX_USER_CONNECTIONS 3 es una restricción histórica compatible con releases previos y no forma parte del alcance de este release.

Alternativa de menor riesgo

Preparar un runner específico de #182 que use un lock remoto y una única sesión PHP/WP por fase:

  1. Adquirir flock -n remoto para impedir dos releases Fe Adulta simultáneos.
  2. En un único PHP cargando wp-load.php, leer y guardar el snapshot completo del cluster de carta.
  3. En otra única sesión PHP, actualizar en orden los cinco posts de carta y remapear sus enlaces. Sin abrir un helper/SSH por post.
  4. En una única sesión PHP de verificación, leer los diez posts, comprobar Polylang, permalinks reales, contenido normalizado y metas TTS.
  5. Mantener Enrique como drafts hasta que esa verificación devuelva un JSON completo y verde; sólo después promover el grupo en serie.
  6. Si falla una fase, restaurar desde el snapshot con una única sesión PHP, conservar los drafts y comentar aquí.

Esto reduce al mínimo la presión sobre MySQL: como máximo un proceso PHP/WP controlado del release por fase, sin ráfagas ni solapamientos. Antes de usarlo debe existir dry-run local y dry-run remoto sólo lectura, ambos registrados en este issue.

## Propuesta de reintento de bajo riesgo — sin cambios MySQL Investigación completada: - No hay código concurrente (`ThreadPool`, `asyncio`, `xargs -P`, `parallel`, etc.) en los runners versionados. - El sincronizador estándar recorre posts en serie, pero cada helper abre un SSH + proceso PHP nuevo. - El runner exacto usado para #182 no fue guardado junto con los artefactos; por tanto no hay evidencia suficiente para afirmar que ejecutó cinco escrituras en paralelo. - El apply registrado duró 32 segundos e hizo cinco drafts, audio y cinco actualizaciones de carta; eso exige prudencia, pero no demuestra concurrencia. ### Decisión operativa **No cambiar MySQL**. `MAX_USER_CONNECTIONS 3` es una restricción histórica compatible con releases previos y no forma parte del alcance de este release. ### Alternativa de menor riesgo Preparar un runner específico de #182 que use un lock remoto y una única sesión PHP/WP por fase: 1. Adquirir `flock -n` remoto para impedir dos releases Fe Adulta simultáneos. 2. En un único PHP cargando `wp-load.php`, leer y guardar el snapshot completo del cluster de carta. 3. En otra única sesión PHP, actualizar en orden los cinco posts de carta y remapear sus enlaces. Sin abrir un helper/SSH por post. 4. En una única sesión PHP de verificación, leer los diez posts, comprobar Polylang, permalinks reales, contenido normalizado y metas TTS. 5. Mantener Enrique como drafts hasta que esa verificación devuelva un JSON completo y verde; sólo después promover el grupo en serie. 6. Si falla una fase, restaurar desde el snapshot con una única sesión PHP, conservar los drafts y comentar aquí. Esto reduce al mínimo la presión sobre MySQL: como máximo un proceso PHP/WP controlado del release por fase, sin ráfagas ni solapamientos. Antes de usarlo debe existir dry-run local y dry-run remoto sólo lectura, ambos registrados en este issue.
Author
Owner

Instrucción operativa vigente para el reintento

Esta nota sustituye cualquier propuesta anterior de cambiar MySQL: no se modifica MySQL ni su límite de conexiones.

Flujo obligatorio

  1. Ejecutar el release con lock remoto y sin concurrencia.
  2. Preparar/usar un runner batch: una sola sesión PHP/WP por fase (snapshot, actualización del cluster, verificación y, si procede, rollback). No lanzar un SSH/PHP/helper por post ni procesos en paralelo.
  3. Hacer dry-run local y dry-run remoto sólo lectura antes de escribir.
  4. Reutilizar los drafts existentes #54997–#55001; no crear un segundo grupo.
  5. Actualizar únicamente la carta existente #54914 y su cluster #54979–#54982; nunca clonar otra carta ES.
  6. Corregir el verificador para usar permalinks reales, contenido ya normalizado/remapeado y validación TTS server-side de drafts.
  7. Crear snapshot fresco antes de escribir. Si cualquier lectura no devuelve JSON válido o un check falla, restaurar el cluster desde ese snapshot y conservar Enrique como drafts.
  8. Publicar Enrique sólo cuando todos los checks corregidos estén verdes.

Gitea: entorno y comentario obligatorio

Antes de cualquier escritura, cargar el entorno correcto y confirmar acceso al Gitea vivo:

set -a
source ~/.hermes/profiles/feadulta/.env
set +a
test -n "$FEA_GITEA_TOKEN"

FEA_GITEA_TOKEN autentica como rafa en gitea.feadulta.com; nunca imprimirlo ni copiarlo en logs. El operador debe comentar resultado, findings, IDs/URLs, dry-runs, snapshot, checks y rollback —si lo hubo— en este mismo issue y verificar que el comentario es visible.

## Instrucción operativa vigente para el reintento Esta nota sustituye cualquier propuesta anterior de cambiar MySQL: **no se modifica MySQL ni su límite de conexiones**. ### Flujo obligatorio 1. Ejecutar el release con lock remoto y sin concurrencia. 2. Preparar/usar un runner batch: una sola sesión PHP/WP por fase (snapshot, actualización del cluster, verificación y, si procede, rollback). No lanzar un SSH/PHP/helper por post ni procesos en paralelo. 3. Hacer dry-run local y dry-run remoto sólo lectura antes de escribir. 4. Reutilizar los drafts existentes `#54997–#55001`; no crear un segundo grupo. 5. Actualizar únicamente la carta existente `#54914` y su cluster `#54979–#54982`; nunca clonar otra carta ES. 6. Corregir el verificador para usar permalinks reales, contenido ya normalizado/remapeado y validación TTS server-side de drafts. 7. Crear snapshot fresco antes de escribir. Si cualquier lectura no devuelve JSON válido o un check falla, restaurar el cluster desde ese snapshot y conservar Enrique como drafts. 8. Publicar Enrique sólo cuando todos los checks corregidos estén verdes. ### Gitea: entorno y comentario obligatorio Antes de cualquier escritura, cargar el entorno correcto y confirmar acceso al Gitea vivo: ```bash set -a source ~/.hermes/profiles/feadulta/.env set +a test -n "$FEA_GITEA_TOKEN" ``` `FEA_GITEA_TOKEN` autentica como `rafa` en `gitea.feadulta.com`; nunca imprimirlo ni copiarlo en logs. El operador debe comentar resultado, findings, IDs/URLs, dry-runs, snapshot, checks y rollback —si lo hubo— en este mismo issue y verificar que el comentario es visible.
Author
Owner

Resultado de release

Estado: bloqueadorollback aplicado
Ejecutado por: Hermes / Luigi — 25-jul-2026 12:16 UTC

Preflight

  • Backup/snapshot: logs/release-issue-182/retry-serial/snapshot.json
  • Carta #54914 antes: snapshot fresco serial del cluster ES/EN/FR/IT/PT
  • Dry-runs: PASS — runner serial con lock remoto atómico; Enrique #54997–#55001 confirmado como grupo Polylang completo en draft; carta #54914/#54979–#54982 confirmada existente y publish.

IDs y URLs en producción

Verificaciones server-side

  • Categorías y autor correctos (dry-run serial)
  • Polylang completo (dry-run serial)
  • Posición única de Enrique correcta
  • Ausencia en Artículos seleccionados
  • Enlaces por idioma / fallbacks justificados
  • Audio y metas correctos

Findings / desviaciones

  • No se modificó MySQL ni sus límites de conexiones.
  • El runner se guardó en logs/release-issue-182/retry_issue182_serial.py; usa lock remoto atómico y una única sesión PHP/WP por fase.
  • La fase de actualización in-place de la carta se ejecutó serialmente, pero la verificación serial falló con un error PHP del propio verificador (TypeError: Cannot access offset of type string on string) antes de devolver JSON válido. Según el plan vigente, se considera check fallido: no se publicó Enrique.

Rollback

  • Aplicado desde el snapshot fresco: restaurados título/contenido del cluster #54914, #54979, #54980, #54981, #54982 en una sola fase PHP/WP serial.
  • Evidencia: logs/release-issue-182/retry-serial/rollback.json.
## Resultado de release **Estado:** `bloqueado` — `rollback aplicado` **Ejecutado por:** Hermes / Luigi — 25-jul-2026 12:16 UTC ### Preflight - Backup/snapshot: `logs/release-issue-182/retry-serial/snapshot.json` - Carta #54914 antes: snapshot fresco serial del cluster ES/EN/FR/IT/PT - Dry-runs: PASS — runner serial con lock remoto atómico; Enrique `#54997–#55001` confirmado como grupo Polylang completo en `draft`; carta `#54914/#54979–#54982` confirmada existente y `publish`. ### IDs y URLs en producción - Enrique ES: #54997 — https://www.feadulta.com/el-tesoro-esta-ya-en-nosotros/ (draft, no publicado) - Enrique EN/FR/IT/PT: #54998 / #54999 / #55000 / #55001 (drafts, no publicados) - Carta ES: #54914 — https://www.feadulta.com/hacia-el-corazon/ - Carta EN/FR/IT/PT: #54979 / #54980 / #54981 / #54982 - Audio: `/web/wp-content/uploads/tts/54997.mp3`, 2,221,101 bytes; metas ya asociadas al ID ES real `#54997`. ### Verificaciones server-side - [x] Categorías y autor correctos (dry-run serial) - [x] Polylang completo (dry-run serial) - [ ] Posición única de Enrique correcta - [ ] Ausencia en Artículos seleccionados - [ ] Enlaces por idioma / fallbacks justificados - [ ] Audio y metas correctos ### Findings / desviaciones - No se modificó MySQL ni sus límites de conexiones. - El runner se guardó en `logs/release-issue-182/retry_issue182_serial.py`; usa lock remoto atómico y una única sesión PHP/WP por fase. - La fase de actualización in-place de la carta se ejecutó serialmente, pero la verificación serial falló con un error PHP del propio verificador (`TypeError: Cannot access offset of type string on string`) antes de devolver JSON válido. Según el plan vigente, se considera check fallido: no se publicó Enrique. ### Rollback - Aplicado desde el snapshot fresco: restaurados título/contenido del cluster `#54914`, `#54979`, `#54980`, `#54981`, `#54982` en una sola fase PHP/WP serial. - Evidencia: `logs/release-issue-182/retry-serial/rollback.json`.
Author
Owner

Corrección del fallo PHP del reintento serial

Causa exacta

El error TypeError: Cannot access offset of type string on string era un bug del verificador local retry_issue182_serial.py, no de WordPress/MySQL:

  • el payload expected enviaba por idioma un string de contenido;
  • el PHP de verificación lo trataba como un objeto y accedía a ['title'] y ['content'].

Corrección y prueba

  • Backup previo: docs/backups/retry-serial-runner-20260725T161834Z/retry_issue182_serial.py.
  • Dry-run server-side previo: PASS; Enrique sigue draft y el cluster de carta sigue publish.
  • Corrección mínima local: cada entrada de expected ahora contiene explícitamente title y content.
  • py_compile: PASS.
  • verify server-side sólo lectura: devolvió JSON válido, sin TypeError ni HTML/Database Error.

Resultado de la verificación post-rollback:

{"checks":["content-C-es","content-C-en","content-C-fr","content-C-it","content-C-pt","position"],"ok":false}

Estos checks rojos son esperados: la carta fue restaurada al snapshot anterior y por tanto aún no contiene el bloque de Enrique. No se ha ejecutado apply ni publish, ni se ha tocado producción durante esta corrección.

El runner ya puede pasar a un nuevo dry-run completo; el siguiente apply sigue requiriendo snapshot fresco y todas las salvaguardas del comentario #430.

## Corrección del fallo PHP del reintento serial ### Causa exacta El error `TypeError: Cannot access offset of type string on string` era un bug del verificador local `retry_issue182_serial.py`, no de WordPress/MySQL: - el payload `expected` enviaba por idioma un string de contenido; - el PHP de verificación lo trataba como un objeto y accedía a `['title']` y `['content']`. ### Corrección y prueba - Backup previo: `docs/backups/retry-serial-runner-20260725T161834Z/retry_issue182_serial.py`. - Dry-run server-side previo: PASS; Enrique sigue `draft` y el cluster de carta sigue `publish`. - Corrección mínima local: cada entrada de `expected` ahora contiene explícitamente `title` y `content`. - `py_compile`: PASS. - `verify` server-side sólo lectura: devolvió JSON válido, sin TypeError ni HTML/Database Error. Resultado de la verificación post-rollback: ```json {"checks":["content-C-es","content-C-en","content-C-fr","content-C-it","content-C-pt","position"],"ok":false} ``` Estos checks rojos son esperados: la carta fue restaurada al snapshot anterior y por tanto aún no contiene el bloque de Enrique. No se ha ejecutado `apply` ni `publish`, ni se ha tocado producción durante esta corrección. El runner ya puede pasar a un nuevo dry-run completo; el siguiente apply sigue requiriendo snapshot fresco y todas las salvaguardas del comentario #430.
Author
Owner

Dry-run completo del runner serial corregido

Estado: PASS para todas las fases de preflight/planificación; no se ejecutó apply ni publish.

Backup y snapshot

  • Backup local previo del runner y snapshot anterior: docs/backups/retry-serial-dryrun-20260725T162323Z/.
  • Snapshot fresco server-side, sólo lectura: logs/release-issue-182/retry-serial/snapshot.json.
  • El snapshot confirma que el cluster actual de carta sigue publish y Enrique #54997–#55001 sigue draft.

Pruebas realizadas

  • py_compile del runner corregido: PASS.
  • dry-run serial server-side: PASS para los diez posts, con grupos Polylang completos y estados esperados.
  • Validación estática del payload de verificación: PASS; los cinco idiomas llevan estructuras title + content válidas.
  • snapshot serial server-side: PASS.
  • verify serial server-side, sólo lectura: JSON válido y sin TypeError, HTML ni Database Error.

Resultado actual de verify:

{"checks":["content-C-es","content-C-en","content-C-fr","content-C-it","content-C-pt","position"],"ok":false}

Es el resultado esperado antes de aplicar: las cinco cartas conservan el snapshot restaurado y no contienen todavía el bloque de Enrique. No hay otros checks rojos (Polylang, estados de drafts, autor/categorías y audio están verdes en el plan/verificador).

El runner queda preparado para un apply futuro con snapshot fresco, lock remoto y rollback, pero esta ejecución no cambió contenido ni estados en producción.

## Dry-run completo del runner serial corregido **Estado:** PASS para todas las fases de preflight/planificación; **no se ejecutó `apply` ni `publish`**. ### Backup y snapshot - Backup local previo del runner y snapshot anterior: `docs/backups/retry-serial-dryrun-20260725T162323Z/`. - Snapshot fresco server-side, sólo lectura: `logs/release-issue-182/retry-serial/snapshot.json`. - El snapshot confirma que el cluster actual de carta sigue `publish` y Enrique `#54997–#55001` sigue `draft`. ### Pruebas realizadas - `py_compile` del runner corregido: PASS. - `dry-run` serial server-side: PASS para los diez posts, con grupos Polylang completos y estados esperados. - Validación estática del payload de verificación: PASS; los cinco idiomas llevan estructuras `title` + `content` válidas. - `snapshot` serial server-side: PASS. - `verify` serial server-side, sólo lectura: JSON válido y sin `TypeError`, HTML ni `Database Error`. Resultado actual de `verify`: ```json {"checks":["content-C-es","content-C-en","content-C-fr","content-C-it","content-C-pt","position"],"ok":false} ``` Es el resultado esperado antes de aplicar: las cinco cartas conservan el snapshot restaurado y no contienen todavía el bloque de Enrique. No hay otros checks rojos (Polylang, estados de drafts, autor/categorías y audio están verdes en el plan/verificador). El runner queda preparado para un apply futuro con snapshot fresco, lock remoto y rollback, pero esta ejecución no cambió contenido ni estados en producción.
Author
Owner

Próximos pasos — después del dry-run PASS

No ejecutar apply todavía. Falta un último guardrail: el verify actual exige que Enrique esté en draft, por lo que sirve para la verificación pre-publicación, pero no para la comprobación final tras publish.

Secuencia segura propuesta

  1. Añadir y probar localmente un modo verify-published de sólo lectura: debe exigir publish para Enrique y conservar las comprobaciones de contenido, Polylang, enlaces/permalinks y audio. Backup + dry-run antes de modificar el runner.
  2. Justo antes del release, ejecutar preflight fresco bajo lock: dos lecturas JSON consecutivas, comprobar Gitea y crear snapshot nuevo del cluster de carta.
  3. Ejecutar apply una sola vez, con el runner batch serial. No publicar todavía.
  4. Ejecutar el verify pre-publicación. Sólo puede continuar si devuelve ok: true sin checks.
  5. Ejecutar publish del grupo #54997–#55001 en su única sesión PHP/WP serial.
  6. Ejecutar inmediatamente verify-published; sólo aceptar ok: true.
  7. Comentar resultados y URLs en #182. Si falla cualquier paso desde apply, restaurar el cluster de carta desde el snapshot fresco; no despublicar ni borrar Enrique sin evidencia de que el publish parcial lo requiera.

No modificar MySQL. No clonar posts ni cartas. No usar paralelismo.

## Próximos pasos — después del dry-run PASS No ejecutar `apply` todavía. Falta un último guardrail: el `verify` actual exige que Enrique esté en `draft`, por lo que sirve para la verificación **pre-publicación**, pero no para la comprobación final tras `publish`. ### Secuencia segura propuesta 1. Añadir y probar localmente un modo `verify-published` de sólo lectura: debe exigir `publish` para Enrique y conservar las comprobaciones de contenido, Polylang, enlaces/permalinks y audio. Backup + dry-run antes de modificar el runner. 2. Justo antes del release, ejecutar preflight fresco bajo lock: dos lecturas JSON consecutivas, comprobar Gitea y crear snapshot nuevo del cluster de carta. 3. Ejecutar `apply` una sola vez, con el runner batch serial. **No publicar todavía.** 4. Ejecutar el `verify` pre-publicación. Sólo puede continuar si devuelve `ok: true` sin checks. 5. Ejecutar `publish` del grupo #54997–#55001 en su única sesión PHP/WP serial. 6. Ejecutar inmediatamente `verify-published`; sólo aceptar `ok: true`. 7. Comentar resultados y URLs en #182. Si falla cualquier paso desde `apply`, restaurar el cluster de carta desde el snapshot fresco; no despublicar ni borrar Enrique sin evidencia de que el publish parcial lo requiera. No modificar MySQL. No clonar posts ni cartas. No usar paralelismo.
Author
Owner

Guardrail verify-published añadido y probado

  • Backup previo del runner: docs/backups/retry-serial-runner-20260725T163016Z/retry_issue182_serial.py.
  • Se añadió el modo server-side de sólo lectura verify-published; exige publish para Enrique y conserva los checks de contenido, Polylang, posición y TTS.
  • py_compile: PASS.
  • Prueba contra el estado actual pre-apply: JSON válido, sin HTML/Database Error ni TypeError:
{"checks":["status-A-es","status-A-en","status-A-fr","status-A-it","status-A-pt","content-C-es","content-C-en","content-C-fr","content-C-it","content-C-pt","position"],"ok":false}

El resultado rojo es esperado antes de aplicar/publicar: Enrique permanece draft y la carta continúa restaurada sin su bloque. No se ejecutó apply, publish, rollback ni ningún cambio MySQL.

El runner queda preparado para la secuencia del comentario #434: preflight fresco → snapshot → apply → verify → publish → verify-published, con rollback del cluster si falla cualquier fase.

## Guardrail `verify-published` añadido y probado - Backup previo del runner: `docs/backups/retry-serial-runner-20260725T163016Z/retry_issue182_serial.py`. - Se añadió el modo server-side de sólo lectura `verify-published`; exige `publish` para Enrique y conserva los checks de contenido, Polylang, posición y TTS. - `py_compile`: PASS. - Prueba contra el estado actual pre-apply: JSON válido, sin HTML/Database Error ni TypeError: ```json {"checks":["status-A-es","status-A-en","status-A-fr","status-A-it","status-A-pt","content-C-es","content-C-en","content-C-fr","content-C-it","content-C-pt","position"],"ok":false} ``` El resultado rojo es esperado antes de aplicar/publicar: Enrique permanece `draft` y la carta continúa restaurada sin su bloque. No se ejecutó `apply`, `publish`, rollback ni ningún cambio MySQL. El runner queda preparado para la secuencia del comentario #434: preflight fresco → snapshot → apply → verify → publish → verify-published, con rollback del cluster si falla cualquier fase.
Author
Owner

Resultado de release

Estado: completado
Ejecutado por: Hermes / Luigi — 25-jul-2026 16:35 UTC

Preflight

  • Backup/snapshot: logs/release-issue-182/retry-serial/snapshot.json
  • Carta #54914 antes: snapshot fresco serial completo del cluster ES/EN/FR/IT/PT.
  • Dry-runs: PASS — dos lecturas JSON consecutivas de los 10 posts, autenticación Gitea vivo como rafa, y runner serial bajo lock remoto.

IDs y URLs en producción

Verificaciones server-side

  • Categorías y autor correctos
  • Polylang completo
  • Posición única de Enrique correcta
  • Ausencia en Artículos seleccionados
  • Enlaces por idioma / fallbacks justificados
  • Audio y metas correctos

Findings / desviaciones

  • No se modificó MySQL ni sus límites.
  • Release ejecutado con lock remoto atómico y una sesión PHP/WP por fase. verify pre-publicación y verify-published devolvieron ambos {"checks":[],"ok":true}.

Rollback

  • No requerido.
## Resultado de release **Estado:** `completado` **Ejecutado por:** Hermes / Luigi — 25-jul-2026 16:35 UTC ### Preflight - Backup/snapshot: `logs/release-issue-182/retry-serial/snapshot.json` - Carta #54914 antes: snapshot fresco serial completo del cluster ES/EN/FR/IT/PT. - Dry-runs: PASS — dos lecturas JSON consecutivas de los 10 posts, autenticación Gitea vivo como `rafa`, y runner serial bajo lock remoto. ### IDs y URLs en producción - Enrique ES: #54997 — https://www.feadulta.com/el-tesoro-esta-ya-en-nosotros/ - Enrique EN/FR/IT/PT: #54998 https://www.feadulta.com/en/the-treasure-is-already-within-us/ ; #54999 https://www.feadulta.com/fr/le-tresor-est-deja-en-nous/ ; #55000 https://www.feadulta.com/it/il-tesoro-e-gia-in-noi/ ; #55001 https://www.feadulta.com/pt/o-tesouro-ja-esta-em-nos/ - Carta ES: #54914 — https://www.feadulta.com/hacia-el-corazon/ - Carta EN/FR/IT/PT: #54979 / #54980 / #54981 / #54982 — cluster existente actualizado in-place. - Audio: `/web/wp-content/uploads/tts/54997.mp3`, 2,221,101 bytes, `https://www.feadulta.com/wp-content/uploads/tts/54997.mp3`, voz `NicoFeadulta2026`. ### Verificaciones server-side - [x] Categorías y autor correctos - [x] Polylang completo - [x] Posición única de Enrique correcta - [x] Ausencia en Artículos seleccionados - [x] Enlaces por idioma / fallbacks justificados - [x] Audio y metas correctos ### Findings / desviaciones - No se modificó MySQL ni sus límites. - Release ejecutado con lock remoto atómico y una sesión PHP/WP por fase. `verify` pre-publicación y `verify-published` devolvieron ambos `{"checks":[],"ok":true}`. ### Rollback - No requerido.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rafa/feadulta#182